Reputation: 5710
Note: This question is basically the same as this one, but for Swift 4 or 5.
Say I have a class that captures a closure:
class CallbackHolder {
typealias Callback = (String) -> Void
var callback: Callback
init(_ callback: @escaping Callback) {
self.callback = callback
}
func useCallback() {
self.callback("Hi!")
}
}
This class simply holds a callback in a variable, and has a function that uses that callback.
Now, say I have a client class that owns such a callback holder. This class wants the callback holder to call one of its methods as the callback:
class Client {
func callback(string: String) {
print(string)
}
lazy var callbackOwner = CallbackOwner(callback: callback)
deinit {
print("deinit")
}
}
This class has a callback, a callback owner that calls that callback, and a deinit that prints something so that we know whether we have a retain cycle (no deinit = retain cycle).
We can test our setup with the following test function:
func test() {
Client().callbackOwner.useCallback()
}
We want the test function to print both Hi!
and deinit
, so that we know that the callback works, and that the client does not suffer from a retain cycle.
The above Client implementation does in fact have a retain cycle -- passing the callback
method to the callback owner causes the owner to retain the client strongly, causing a cycle.
Of course, we can fix the cycle by replacing
lazy var callbackOwner = CallbackOwner(callback: callback)
with
lazy var callbackOwner = CallbackOwner(callback: { [weak self] in
self?.callback($0)
})
This works, but:
So I am looking for a better way. I would like to capture the callback weakly at the CallbackOwner, not at the client. That way, new clients don't have to be aware of the danger of the retain cycle, and they can use my CallbackOwner in the most intuitive way possible.
So I tried to change CallbackOwner's callback property to
weak var callback: Callback?
but of course, Swift only allows us to capture class types weakly, not closures or methods.
At the time when this answer was written, there did not seem to be a way to do achieve what I'm looking for, but that was over 4 years ago. Has there been any developments in recent Swift versions that would allow me to pass a method to a closure-capturing object without causing a retain cycle?
Upvotes: 1
Views: 256
Reputation: 86651
Well one obvious way to do this would be to not hold a reference to CallbackHolder
in Client
i.e.
class Client {
func callback(string: String) {
print(string)
}
var callbackOwner: CallbackHolder { return CallbackHolder(callback) }
deinit {
print("deinit")
}
}
In the above case, I'd probably make CallbackHolder
a struct
rather than a class.
Personally, I don't see any value in wrapping the callback in a class at all. Just pass the callback around, or even Client
. Maybe make a protocol for it to adhere to
protocol Callbackable
{
func callback(string: String)
}
extension Callbackable
{
func useCallback() {
self.callback(string: "Hi!")
}
}
class Client: Callbackable {
func callback(string: String) {
print(string)
}
deinit {
print("deinit")
}
}
func test(thing: Callbackable) {
thing.useCallback()
}
test(thing: Client())
Upvotes: 1