非“@objc”方法不满足“@objc”协议的可选要求

Non-'@objc' method does not satisfy optional requirement of '@objc' protocol

概览:

编译器警告:

Non-'@objc' method 'presentationController(_:viewControllerForAdaptivePresentationStyle:)' does not satisfy optional requirement of '@objc' protocol 'UIAdaptivePresentationControllerDelegate'

版本:

尝试次数:

问题:

代码:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}


class A : UIViewController, P1 {

}

虽然我想我可以回答你的问题,但这不是你会喜欢的答案。

TL;DR: @objc 函数当前可能不在协议扩展中。您可以改为创建一个基础 class,尽管这不是理想的解决方案。

协议扩展和Objective-C

首先,这个 question/answer (Can Swift Method Defined on Extensions on Protocols Accessed in Objective-c) 似乎表明,由于协议扩展在后台调度的方式,协议扩展中声明的方法对 objc_msgSend() 函数,因此对 Objective-C 代码不可见。由于您尝试在扩展中定义的方法需要对 Objective-C 可见(因此 UIKit 可以使用它),它会因为不包括 @objc 而对您大喊大叫,但是一旦您这样做了包含它,它会对你大喊大叫,因为 @objc 在协议扩展中是不允许的。这可能是因为协议扩展当前无法对 Objective-C.

可见

我们还可以看到,添加 @objc 后的错误消息指出“@objc 只能与 classes 的成员、@objc 协议和 [=99 的具体扩展一起使用=]是的。”这不是class; @objc 协议的扩展与协议定义本身(即在需求中)不同,"concrete" 一词表明协议扩展不算作具体的 class 扩展.

解决方法

不幸的是,当默认实现必须对 Objective-C 框架可见时,这几乎完全阻止了您使用协议扩展。起初,我想也许 @objc 在你的协议扩展中是不允许的,因为 Swift 编译器不能保证符合类型是 classes(即使你已经明确指定 UIViewController).所以我对 P1 提出了 class 的要求。这没有用。

也许唯一的解决方法是在这里简单地使用基 class 而不是协议,但这显然不是完全理想的,因为 class 可能只有一个基 class 但符合多种协议。

如果您选择走这条路,请考虑这个问题 ()。 Swift 3 中的另一个当前问题似乎是 subclasses 不会自动继承其 superclass 的可选协议要求实现。该问题的答案使用 @objc 的特殊改编来解决它。

报告问题

我认为从事 Swift 开源项目的人员已经在讨论这个问题,但您可以通过使用 Apple's Bug Reporter, which would likely eventually make its way to the Swift Core Team, or Swift's bug reporter. Either of these may find your bug too broad or already known, however. The Swift team may also consider what you are looking for to be a new language feature, in which case you should first check out the mailing lists.

来确保他们知道

更新

2016 年 12 月,此问题 was reported 到 Swift 社区。该问题仍标记为中等优先级未解决,但添加了以下评论:

This is intended. There is no way to add the implementation of the method to every adopter, since the extension could be added after the conformance to the protocol. I suppose we could allow it if the extension is in the same module as the protocol, though.

但是,由于您的协议与您的扩展位于同一模块中,因此您可以在 Swift 的未来版本中执行此操作。

更新 2

2017 年 2 月,was officially closed 由 Swift 核心团队成员之一 "Won't Do" 发出以下消息:

This is intentional: protocol extensions cannot introduce @objc entry points due to limitations of the Objective-C runtime. If you want to add @objc entry points to NSObject, extend NSObject.

扩展 NSObject 甚至 UIViewController 不会完全达到您想要的效果,但不幸的是,它看起来不太可能。

在(非常)长期的未来,我们也许能够完全消除对 @objc 方法的依赖,但那个时候可能不会很快到来,因为 Cocoa 框架目前还没有用 Swift 编写(直到它有一个稳定的 ABI 才可以)。

更新 3

截至 2019 年秋季,这已不再是一个问题,因为越来越多的 Apple 框架是用 Swift 编写的。例如,如果您使用 SwiftUI 而不是 UIKit,您就完全回避了这个问题,因为在引用 SwiftUI 方法时永远不需要 @objc

用 Swift 编写的 Apple 框架包括:

  • SwiftUI
  • RealityKit
  • 合并
  • CryptoKit

既然 Swift 在 Swift 5.0 和 5.1 中分别是正式的 ABI 和模块稳定,那么人们希望这种模式会随着时间的推移而持续。

在我使用的 swift 框架中启用 'module stability'(打开 'Build libraries for distribution')后,我只是 运行 进入了这个。

我得到的是这样的:

class AwesomeClass: LessAwesomeClass {
...
}

extension AwesomeClass: GreatDelegate {
  func niceDelegateFunc() {
  }
}

扩展中的函数有这些错误:

  • '@objc' 'LessAwesomeClass' 的 subclass 扩展中的实例方法需要 iOS 13.0.0

  • 非'@objc'方法'niceDelegateFunc'不满足'@objc'协议的要求'GreatDelegate'

将函数移动到 class 而不是扩展中解决了这个问题。

这是另一个解决方法。我 运行 也遇到了这个问题,但还不能从 UIKit 切换到 SwiftUI。将默认实现移动到公共基础 class 对我来说也不是一个选择。我的默认实现非常广泛,所以我真的不想重复所有代码。我最终使用的解决方法是在协议中使用包装函数,然后简单地从每个 class 调用这些函数。不漂亮,但可能比替代方案更好,视情况而定。您的代码将如下所示:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {
}

extension P1 where Self : UIViewController {
    func wrapPresentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}

class A : UIViewController, P1 {
    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return wrapPresentationController(controller, viewControllerForAdaptivePresentationStyle: style)
    }
}