非 '@objc' 方法不满足 '@objc' 协议的可选要求。

127

概览:

  • 我有一个协议P1,它提供了Objective-C可选函数的默认实现。
  • 当我提供可选函数的默认实现时,会出现警告。

编译器警告:

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

版本:

  • Swift:3
  • Xcode:8(公开版)

尝试过的方法:

  • 尝试添加 @objc 但没有帮助

问题:

  • 如何解决这个问题?
  • 有没有解决办法?

代码:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

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


class A : UIViewController, P1 {

}

你有最新版本的Xcode吗?如果我删除@objc,我就永远不会出现错误。 - Qbyte
我正在使用Xcode 8(最新公共版本)。没有错误,但会有一个警告。 - user1046037
3个回答

215

虽然我认为我可以回答你的问题,但这并不是你想要的答案。

TL;DR:@objc函数可能目前还不能在协议扩展中使用。您可以创建一个基类,但这并不是理想的解决方案。

协议扩展和Objective-C

首先,这个问题/答案(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只能与类的成员、@objc协议和类的具体扩展一起使用”。但这不是一个类;对@objc协议的扩展并不等同于在协议定义本身中(即在要求中)声明它,而“concrete”一词则表明协议扩展不被视为具体类扩展。

解决方法

不幸的是,这基本上完全阻止您在默认实现必须对Objective-C框架可见时使用协议扩展。起初,我以为可能不允许在协议扩展中使用@objc是因为Swift编译器无法保证符合类型将是类(即使您已经明确指定了UIViewController)。所以我在P1上放置了一个class要求。但这没有起作用。

也许唯一的解决方法就是使用基类而不是协议,但这显然并不完美,因为一个类只能有一个基类,但可以符合多个协议。

如果您选择这种方法,请考虑这个问题(Swift 3 ObjC Optional Protocol Method Not Called in Subclass)。似乎在Swift 3中另一个当前问题是子类不会自动继承其超类的可选协议要求实现。那个问题的答案使用了@objc的特殊适配来解决它。

报告问题

我认为这已经在致力于Swift开源项目的人员之间讨论了,但您可以使用Apple的Bug ReporterSwift的bug reporter,以确保他们知道。这两者可能会认为您的错误太广泛或已知。Swift团队也可能认为您正在寻找一个新的语言功能,在这种情况下,您应该先查看邮件列表

更新

在2016年12月,这个问题被报告给了Swift社区。该问题仍然标记为开放状态,优先级为中等,但添加了以下注释:

这是有意的。没有办法将方法的实现添加到每个采用者中,因为扩展可能会在遵守协议之后添加。我想,如果扩展位于与协议相同的模块中,我们可以允许使用它。

然而,由于您的协议与扩展位于同一模块中,所以您可能可以在未来的Swift版本中做到这一点。

更新2

在2017年2月,Swift核心团队的一位成员正式将此问题标记为“不会做”,并留下以下信息:

这是有意为之的:由于Objective-C运行时的限制,协议扩展不能引入@objc入口点。如果您想向NSObject添加@objc入口点,请扩展NSObject。

扩展NSObject甚至UIViewController也无法完全实现您想要的效果,但遗憾的是目前看来这似乎是不可能的。

在(非常)长远的未来,我们可能能够完全消除对@objc方法的依赖,但这个时间很可能不会很快到来,因为Cocoa框架目前没有用Swift编写(在Swift稳定ABI之前无法使用)。

更新3

截至2019年秋季,这种情况正在变得不那么严重,因为越来越多的Apple框架正在用Swift编写。例如,如果您使用SwiftUI而不是UIKit,则完全可以避开此问题,因为在引用SwiftUI方法时永远不需要使用@objc

用Swift编写的Apple框架包括:

  • SwiftUI
  • RealityKit
  • Combine
  • CryptoKit

人们预计这种模式会随着时间的推移继续下去,现在Swift 5.0和5.1已经正式成为ABI和模块的稳定版。


1
@user1046037 我也是,因为我可以看到自己在未来的开发中会遇到这个问题很多次。 - Matthew Seaman
2
你是正确的,尽管现在还没有其他选择,但你的答案仍然适用于 Swift 4 - user1046037
2
我的代码之前一直可以运行,但是在 Xcode 的后续版本中突然出现了问题,这真的很令人恼火。Objective-C 协议中有很多可选方法。 - Lucas van Dongen

5

在使用我常用的 Swift 框架时,我启用了“模块稳定性”(打开“为分发构建库”),然后遇到了这个问题。

我的情况是这样的:

class AwesomeClass: LessAwesomeClass {
...
}

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

扩展中的函数存在以下错误:

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

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

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


0

这里有另一个解决方法。我也遇到了这个问题,但目前还不能从UIKit切换到SwiftUI。将默认实现移动到公共基类对我来说也不是一个选项。我的默认实现非常广泛,所以我真的不想复制所有那些代码。我最终使用的解决方法是在协议中使用包装器函数,然后从每个类中简单地调用这些函数。虽然不太美观,但根据情况可能比替代方案更好。您的代码将如下所示:

@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)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接