一个旨在提供一个更优秀的 Delegate
模式的元库。该模式在这里 和 这里 有详细描述。
与常见的 Apple 协议-委托模式 不同,使用简单的 Delegate
对象来进行通信。
class ClassA {
let onDone = Delegate<(), Void>()
func doSomething() {
// ...
onDone()
}
}
class MyClass {
func askClassAToDoSomething() {
let a = ClassA()
a.onDone.delegate(on: self) { (self, _) in
self.jobDone()
}
a.doSomething()
}
private func jobDone() {
print("🎉")
}
}
Delegate
以更少的代码和更紧凑的结构完成相同的工作。只需比较上面使用正式协议-委托模式的相同实现。
protocol ClassADelegate {
func doSomethingIsDone()
}
class ClassA {
weak var delegate: ClassADelegate?
func doSomething() {
// ...
delegate?.doSomethingIsDone()
}
}
class MyClass {
func askClassAToDoSomething() {
let a = ClassA()
a.delegate = self
a.doSomething()
}
private func jobDone() {
print("🎉")
}
}
extension MyClass: ClassADelegate {
func doSomethingIsDone() {
self.jobDone()
}
}
没有人喜欢编写样板代码,对吗?
乍一看,你可能会认为 Delegate
有些过度设计,可以用类似这样的存储属性来代替。
class ClassA {
var onDoneProperty: (() -> Void)?
//...
}
它会造成强引用,而且我发现这非常容易导致意外的循环引用。
class MyClass {
var a: ClassA = ClassA()
func askClassAToDoSomething() {
a.onDoneProperty = {
// Retain cycle!!
self.jobDone()
}
}
为了打破循环引用,在大多数情况下你必须记住使用 [weak self]
,并且在使用 self
之前,还需要检查 self
是否为空。
class MyClass {
var a: ClassA = ClassA()
func askClassAToDoSomething() {
a.onDoneProperty = { [weak self] in
guard let self = self else { return }
self.jobDone()
}
}
又是样板代码!而且,如果 onDoneProperty
需要在多层之间持有调用者,情况会变得更加复杂。
Delegate
在内部以弱引用的方式持有 target
,并在委托被调用时,向你提供 target
的“强引用化”影子版本。这样你就可以免费获得正确的内存管理,并专注于你的工作,完全无需编写样板代码。
a.onDone.delegate(on: self) { // This `self` is the delegation target. `onDone` holds a weak ref of it.
(self, _) in // This `self` is a shadowed, non-option type.
self.jobDone() // Using of this `self` does not create retain cycle.
}
要传递参数或接收返回值,只需声明 Delegate
的泛型类型。
class DataController {
let onShouldShowAtIndexPath = Delegate<IndexPath, Bool>()
func foo() {
let currentIndexPath: IndexPath = // ...
let shouldShow: Bool = onShouldShowAtIndexPath(currentIndexPath)
if shouldShow {
show()
}
}
}
// Caller Side
dataSource.onShouldShowAtIndexPath.delegate(on: self /* : Target */ ) { (self, indexPath) in
// This block has a type of `(Target, IndexPath) -> Bool`.
return indexPath.row != 0
}
唯一的注意事项是,请务必始终在委托代码块中使用影子 self
。例如,这样做会导致退回到旧的 onXXX
属性方式,并引发循环引用。
a.onDone.delegate(on: self) { (_, _) in
self.jobDone()
}
表面上看,你可以使用“相同”的 self
,但实际上在上面的代码中,你使用的是“真正”的强引用 self
。 不要将代码块的第一个输入参数标记为 _
,并始终将其命名为 self
,这样可以避免这类问题。