After introducing ARC memory management was simplified a lot, but still could not be forgotten. For sure every developer working with either Objective-C or Swift had to deal with retain cycles. There are lots of in depth posts about this topic and I do not want to rephrase them, so instead I will link to my favourite from krakendev. Unfortunately this post does not cover everything and I would like to mention some specific case which I encountered not that long ago.
Imagine following example, where private member function is passed as a closure argument:
import Foundation
class RequestSender {
func sendRequest(success: @escaping () -> Void) {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.5) {
success()
}
}
}
class Dispatcher {
private let requestSender = RequestSender()
private let dispatchGroup = DispatchGroup()
deinit {
print("Deinitialized")
}
func start() {
dispatchGroup.enter()
requestSender.sendRequest(success: handleResponse)
}
func wait() {
dispatchGroup.wait(timeout: .now() + .seconds(1))
}
private func handleResponse() {
print("Response handled")
dispatchGroup.leave()
}
}
let dispatcher = Dispatcher()
dispatcher.start()
dispatcher.wait()
The problem with the above code is that sendRequest returns its result using @escaping closure from a background queue.
Passing a private handleResponse function as a closure argument creates a retain cycle, hence deinit is never called on Dispatcher object.
The problem could be easily solved by either:
- inlineing handleResponse content withing the closure where self is weak/unowned
requestSender.sendRequest(success: { [unowned self] in
print("Response handled")
self.dispatchGroup.leave()
})
- wrapping handleResponse with another closure where self is weak/unowned
requestSender.sendRequest(success: { [unowned self] in
self.handleResponse()
})
The above code looks ugly and is less compact. What if we created a helper function allowing us to wrap up this code?
func unownedClosure<T: AnyObject, A: Any>(_ obj: T, _ method: @escaping (T) -> (Void) -> A) -> (Void) -> A {
return { [unowned obj] in
method(obj)()
}
}
and then use the following code?
requestSender.sendRequest(success: unownedClosure(self, Dispatcher.handleResponse))
Not perfect, but definitely more compact. Anyway, I think that this is something that could be addressed in new version of Swift.
Check out my GitHub project with Swift One-Liners, where you can find this exemplary wrapper, extended with versions having different closure signatures.
If you enjoyed this article, please follow me on twitter: @tgebarowski