虽然我认为我可以回答你的问题,但这并不是你想要的答案。
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 Reporter或Swift的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和模块的稳定版。
@objc
,我就永远不会出现错误。 - Qbyte