At the first glance, you may think Delegate is an over-work and can be replaced by a stored property like this:
class ClassA {
var onDoneProperty: (() -> Void)?
//...
}
It creates a strong holding, and I found it is really easy to create an unexpected cycle:
class MyClass {
var a: ClassA = ClassA()
func askClassAToDoSomething() {
a.onDoneProperty = {
// Retain cycle!!
self.jobDone()
}
}
You have to remember [weak self] for most cases to break the cycle, it also requires you to check self before using it:
class MyClass {
var a: ClassA = ClassA()
func askClassAToDoSomething() {
a.onDoneProperty = { [weak self] in
guard let self = self else { return }
self.jobDone()
}
}
Boilerplate code again! And things would become more complicated if the onDoneProperty needs to holds caller across multiple layers.
How
Delegate holds the target in a weak way internally, and provide a “strongified” shadowed version of the target to
you when the delegate is called. So you can get the correct memory management for free and focus on your work with no boilerplate code at all.
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.
}
To pass some parameters or receive a return type, just declared the Delegate‘s generic types:
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
}
Caution
The only caution is, please always use the shadowed self in the delegation block. Say, this would cause a
regression to the old onXXX property way and causes a retain cycle:
a.onDone.delegate(on: self) { (_, _) in
self.jobDone()
}
It seems that you can use the “same” self, but actually in the code above you are using the “real” strong self. Do not
mark the first input parameter of block as _ and always give it a name of self then you can prevent this.
Delegate
A meta library to provide a better
Delegate
pattern described here and here.Usage
Instead of a regular Apple’s protocol-delegate pattern, use a simple
Delegate
object to communicate:Why
Compare to regular delegation
Delegate
does the same thing with much less code and compact structure. Just compare with the same work above in a formal protocol-delegate pattern.No one loves to write boilerplate code, do you?
Compared to
onXXX
propertyAt the first glance, you may think
Delegate
is an over-work and can be replaced by a stored property like this:It creates a strong holding, and I found it is really easy to create an unexpected cycle:
You have to remember
[weak self]
for most cases to break the cycle, it also requires you to checkself
before using it:Boilerplate code again! And things would become more complicated if the
onDoneProperty
needs to holds caller across multiple layers.How
Delegate
holds thetarget
in aweak
way internally, and provide a “strongified” shadowed version of the target to you when the delegate is called. So you can get the correct memory management for free and focus on your work with no boilerplate code at all.To pass some parameters or receive a return type, just declared the
Delegate
‘s generic types:Caution
The only caution is, please always use the shadowed
self
in the delegation block. Say, this would cause a regression to the oldonXXX
property way and causes a retain cycle:It seems that you can use the “same”
self
, but actually in the code above you are using the “real” strongself
. Do not mark the first input parameter of block as_
and always give it a name ofself
then you can prevent this.To Do