在objc时代,如果我们想在已有的协议上增加一个方法,并为实现该协议的类增加一个共有的功能,一种常见的做法是将代码拷贝到每一个实现该协议的类中,这是一种笨拙而不便维护的方式。
swift2.0引入了protocol extension,可以对已有的协议添加拓展,并提供默认实现,在所有遵守协议的实例类型中,即使我们什么也不做,也可以编译通过并调用默认实现。
import Foundation
protocol ExampleProtocol {
func method()
}
extension ExampleProtocol {
func method() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
}
ExampleClass().method() //print hi
在日常开发中,还有一处可以用到extension协议拓展。objc中我们可以通过@optional关键字,声明可选接口类型,表示实例不一定要提供实现,而swift的protocol所有的方法都必须提供实现。
那么如果我们想在swift中,实现可选协议该怎么办呢?一种可行的方案是在声明protocol的时候加上关键字@objc,其本质上声明一个objc的协议,并且配合@objc optional关键字实现。
import Foundation
@objc protocol ExampleProtocol {
@objc optional func method()
}
class ExampleClass :ExampleProtocol {
} //build succeeded
这样声明的协议,本质是一个objc的协议,并且只用由class类型实例可以遵守,对swift中struct的enum不可用。
在swift2.0中可依靠extension提供一种更优雅的方式。extension可以为方法编写默认实现,这样即使我们的实力类型不写任何实现,也可用通过编译。
protocol ExampleProtocol {
func optionalMethod() //可选
func necessaryMethod() //必须实现
}
extension ExampleProtocol {
func optionalMethod() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
func necessaryMethod() {
print("necessaryMethod")
}
//由于extension为optionalMethod提供了默认实现
//所以我们这里可以不再重写对应的实现
}
同样,如果在protocol中没有显示声明的方法口,而只在extension中提供了默认实现,也是可以作为可选接口,并通过编译。
protocol ExampleProtocol {
}
extension ExampleProtocol {
func optionalMethod() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
}
//build succeeded
这种在protocol中没有显示声明的方法,而直接在extension中提供了默认实现,容易产生一种迷惑。比如我们定义了这样的一个接口和它的一个扩展:
protocol ExampleProtocol {
//协议中显示声明的方法
func explicitMethod()
}
extension ExampleProtocol {
//协议中显示声明的方法
func explicitMethod() {
print("hi")
}
//协议中未显示声明的方法
func extensionMethod() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
func explicitMethod() {
print("hello")
}
func extensionMethod() {
print("hello")
}
}
在调用的时候,没有疑问,2个方法输出的都是hello:
let cls = ExampleClass()
cls.explicitMethod() //hello
cls.extensionMethod() //hello
而如果我们将cls的类型,强转为协议类型,考虑此时的输出是什么?
let cls = ExampleClass() as ExampleProtocol
cls.explicitMethod() //hello
cls.extensionMethod() //hi
此时的输出似乎出乎了我们的意料,我们都知道cls的真实类型是ExampleClass,并且我们重写了协议的实现,而运行时却输出了extension中的默认实现。
产生这种问题的原因是,由于我们没有显示声明extensionMethod,这个方法实际上变成了可选类型,没有任何规定遵守该协议的类型必须实现,而我们的编译器认为extensionMethod有能未被当前类型的实例实现,转而不使用动态派发的机制,而是在编译期就确定调用哪个具体的实现,以保证安全。
而对于方法explicitMethod,由于在protocol中显示的声明了,因此可以确定遵守该协议的实例一定是实现了该方法(这里不管是重写实现,还是extension的默认实现),可以大胆的使用动态派发机制,寻找方法的最终实现。
总结一下:
- extension允许我们为已有协议增加方法或者增加方法的默认实现。
- swift protocol中显示声明的方法,必须实现,但extension提供了默认实现的方法,实例类型可以不提供显示的具体实现(隐示实现了extension的实现)。
- extension的默认实现可以让方法变为可选,即实例不需要提供最终实现。
- 如果方法没有在protocol中显示的声明,类型推断得到是实例类型将会调用实例的最终实现(如果有,没有则调用extension中的默认实现),而如果类型推断的是协议类型,将会调用extension的默认实现。
本文为读书笔记,参考连接
PROTOCOL EXTENSION
可选接口和接口扩展