sss
delegate
Cocoa
- 开发中协议-委托 (protocol-delegate) 模式是⼀种常⽤的设计模式,它贯穿于整个 Cocoa 框架中,for clear relationship and decoupling
- 在 ARC 中,对于⼀般的 delegate,我们会在声明中将其指定为 weak,在这个 delegate 实际的对 象被释放的时候,会被重置回 nil 。这可以保证即使 delegate 已经不存在时,我们也不会由于访 问到已被回收的内存⽽导致崩溃。ARC 的这个特性杜绝了 Cocoa 开发中⼀种⾮常常见的崩溃错 误,
ISSUE: Swift 的 protocol 是可以被除了 class 以外的其他类型遵守的
- 对于像 struct 或是 enum 这样的类型,本⾝就不通过引⽤计数来管理内存,所以也不可能⽤ weak 这样的 ARC 的概 念来进⾏修饰。
- 想要在 Swift 中使⽤ weak delegate,我们就需要将 protocol 限制在 class 内。
- ⼀种做法是将 protocol 声明为 Objective-C 的,这可以通过在 protocol 前⾯加上 @objc 关键字来达到, Objective-C 的 protocol 都只有类能实现,因此使⽤ weak 来修饰就合理了:
@objc protocol MyClassDelegate {
func method()
}
// 另⼀种可能更好的办法是在 protocol 声明的名字后⾯加上 class ,
// 这可以为编译器显式地指明这个 protocol 只能由 class 来实现。
protocol MyClassDelegate: class {
func method()
}
1. Protocol Extension
- default implementation
provide optional methods
如果类型推断得到的是实际的类型
那么类型中的实现将被调⽤;如果类型中没有实现的话,那么协议扩展中的默认实现将
被使⽤
如果类型推断得到的是协议,⽽不是实际类型
并且⽅法在协议中进⾏了定义,那么类型中的实现将被调⽤;如果类型中没有实现,那
么协议扩展中的默认实现被使⽤
否则 (也就是⽅法没有在协议中定义),扩展中的默认实现将被调⽤
/// protocol
protocol MyProtocol {
func method()
}
/// protocol extension
extension MyProtocol {
// provide default implementation
func method() {
print("Called")
}
/// provide optional implementation
func methodOptional() {
print("Method Optional")
}
}
/// conform protocol
struct MyStruct: MyProtocol {
// override the default implementation
func method() {
print("Called in struct")
}
func methodOptional() {
print("methodOptional Called in struct")
}
}
let testStruct = MyStruct()
testStruct.method() // Called in struct
testStruct.methodOptional() // methodOptional Called in struct
let testPro = testStruct as Myprotocol
testPro.method() // Called in struct
testPro.methodOptional() // methodOptional, called in MyProtocol
testStruct 和 testPro 是同样对象,只不过我们通过 as 告诉编译器我们在这⾥需要的类型是 Myprotocol 。
但是这时候在这个同样的对象上调⽤同样的⽅法调⽤却得到了不同的结果,发⽣了什么?
我们可以看到,对 testPro 调⽤ methodOptional 实际上是协议扩展中的⽅法被调⽤了,⽽不是 testPro 实例中的
⽅法被调⽤。
我们不妨这样来理解:对于 method ,因为它在 protocol 中被定义了,因此对于⼀个被声明为遵守协议的类型的实例 (也就是对于 testPro ) 来说,可以确定实例必然实现了 method ,我们可以放⼼⼤胆地⽤动态派发的⽅式使⽤最终的实现 (不论它是在类型中的具体实现,还是在协议扩展中的默认实现);
但是对于 methodOptional 来说,我们只是在协议扩展中进⾏了定义,没有任何规说它必须在最终的类型中被实现。在使⽤时,因为 testPro 只是⼀个符合 Myprotocol 协议的实例,编译器对 methodOptional 唯⼀能确定的只是在协议扩展中有⼀个默认实现,因此在调⽤时,⽆法确定安全,也就不会去进⾏动态派发,⽽是转⽽编译期间就确定的默认实现。
也许在这个例⼦中你会觉得⽆所谓,因为实际中估计并不会有⼈将⼀个已知类型实例转回协议类
型。但是要考虑到如果你的⼀些泛型 API 中有类似的直接拿到⼀个协议类型的结果的时候,调⽤
它的扩展⽅法时就需要特别⼩⼼了:⼀般来说,如果有这样的需求的话,我们可以考虑将这个协
议类型再转回实际的类型,然后进⾏调⽤。
2. Protocol in Swift and OC
swift protocol can be conformed by the Class, Struct, Enum
Objective C protocol can be only conformed by the Class (Objective C Object or child class from NSObject)
add constraints to protocol extensions
/// Objective-C optional Protocol
@objc protocol OptionalProtocol {
@objc optional func optionalMethod() // 可选
func necessaryMethod() // 必须
@objc optional func anotherOptionalMethod() // 可选
}
/// ⼀个不可避免的限制是,使⽤ @objc 修饰的 protocol 就只能被 class 实现了,
/// 也就是说,对于 struct 和 enum 类型,我们是⽆法令它们所实现的协议中含有可选⽅法或者属性的。
/// 另外,实现它的 class 中的⽅法还必须也被标注为 @objc ,或者整个类就是继承⾃ NSObject 。
/// 这对我们写代码来说是⼀种很让⼈郁闷的限制。
3. Protocol with associated type
// 在 Tiger 通过 typealias 具体指定 F 为 Meat 之前, Animal 协议中并不关⼼ F 的具体类型,
// 只需要满⾜协议的类型中的 F 和 eat 参数⼀致即可。如此⼀来,我们就可以避免在 Tiger 的
// eat 中进⾏判定和转换了。
protocol Food { }
protocol Animal {
func eat(_ food: Food)
}
struct Meat: Food { }
struct Grass: Food { }
// protocol with accociatedtype
protocol Animal {
associatedtype F
func eat(_ food: F)
}
// tiger will assign the type of F
struct Tiger: Animal {
typealias F = Meat
func eat(_ food: Meat) {
print("eat \(meat)")
}
}
不过这⾥忽视了被吃的必须是 Food 这个前提。
associatedtype 声明
中可以使⽤冒号来指定类型满⾜某个协议,
另外,在 Tiger 中只要实现了正确类型的 eat , F
的类型就可以被推断出来,所以我们也不需要显式地写明F
protocol Animal {
associatedtype F: Food
func eat(_ food: F)
}
struct Tiger: Animal {
func eat(_ food: Meat) {
print("eat \(meat)")
}
}
struct Sheep: Animal {
func eat(_ food: Grass) {
print("eat \(food)")
}
}
不过在添加 associatedtype 后,
Animal 协议就不能被当作独⽴的类型使⽤了。试想我们有⼀个函
数来判断某个动物是否危险:
func isDangerous(animal: Animal) -> Bool {
if animal is Tiger {
return true
} else {
return false
}
}
Swift 需要在编译时确定所有类型,这⾥因为 Animal 包含了⼀个不确定的类型 associatedtype F,所以随
着 Animal 本⾝类型的变化,其中的 F 将⽆法确定 (试想⼀下如果在这个函数内部调⽤ eat 的情
形,你将⽆法指定 eat 参数的类型)。在⼀个协议加⼊了像是 associatedtype 或者 Self 的约束
后,它将只能被⽤为泛型约束,⽽不能作为独⽴类型的占位使⽤,也失去了动态派发的特性。也
就是说,这种情况下,我们需要将函数改写为泛型:
func isDangerous<T: Animal>(animal: T) -> Bool {
if animal is Tiger {
return true
} else {
return false
}
}
isDangerous(animal: Tiger()) // true
isDangerous(animal: Sheep()) // false
4. mutating method in Protocol
- Swift's protocol can be implemented by class, struct, enum
- 因为这个原因,我们 在写给别⼈⽤的协议时需要多考虑是否使⽤ mutating 来修饰⽅法, ⽐如定义为 mutating func myMethod() 。
Swift 的 mutating 关键字修饰⽅法是为了能在该⽅法中修改 struct 或是 enum 的变
量,所以如果你没在协议⽅法⾥写 mutating 的话,别⼈如果⽤ struct 或者 enum 来实现这个协
议的话,就不能在⽅法⾥改变⾃⼰的变量了在协议⾥⽤ mutating 修饰
⽅法,对于 class 的实现是完全透明,可以当作不存在的
在使⽤ class 来实现带有 mutating 的⽅法的协议时,具体实现的前⾯是不需要加
mutating 修饰的,
因为 class 可以随意更改⾃⼰的成员变量