而不是使用普通的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
class MyClass {
var a: ClassA = ClassA()
func askClassAToDoSomething() {
a.onDoneProperty = { [weak self] in
guard let self = self else { return }
self.jobDone()
}
}
又是样板代码!而且,如果onDoneProperty
需要在不同层之间持有调用者,事情会变得更加复杂。
Delegate
以弱引用的方式内部持有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
,这样您就可以防止这种情况。
- 异步支持。