Tomasz Gebarowski

Closures argument + member function = retain cycle?

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:

  requestSender.sendRequest(success: { [unowned self] in
    print("Response handled")
    self.dispatchGroup.leave()
  })
  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