Stop weak-strong dance


Closures in Swift are extremely useful, they are interchangeable with functions and that creates a lot of opportunities for useful use-cases. One thing we have to be careful when using them is to avoid retain cycles.

We have to do it so often that it begs the question:

Can we improve the call-site API?

The usual way of dealing with that is using either unowned or weak capture, wheres unowned requires almost no boilerplate, using weak usually requires this annoying dancing pattern:

obj.closure = { [weak self, weak other] some, arguments in 
    guard let strongSelf = self else { return }
    /// ... code
}

I find this ugly, lets instead create strongify function and turn all those calls into something like:

obj.closure = strongify(self, other) { instance, other, some, arguments in
    /// ... code
}

strongify function is very simple:

func strongify<Context: AnyObject, Arguments>(_ context: Context?, closure: @escaping (Context, Arguments) -> Void) -> (Arguments) -> Void {
    return { [weak context] arguments in
        guard let strongContext = context else { return }
        closure(strongContext, arguments)
    }
}

Its a function that:

  • takes a single Context object that is a class, otherwise weak makes no sense
  • takes a closure that accepts context and arguments
  • returns an closure without the Context variable, that you can use in your original API (or 3rd party code)

This variant will work with single context value and arbitrary number of arguments, but Swift will wrap all your arguments into a tuple, so the call-site would look like this:

obj.closure = strongify(other) { instance, arguments in
    let (some, arguments) = arguments
    /// code
}

This is not ideal, we are trying to avoid unnecessary boilerplate. We can just generate more specific variants of this function that would allow us to have the original API example I showed.

func strongify<Context: AnyObject, Context2: AnyObject, Argument1, Argument2>(_ context: Context?, _ context2: Context2?, closure: @escaping (Context, Context2, Argument1, Argument2) -> Void) -> (Argument1, Argument2) -> Void {
    return { [weak context, weak context2] argument1, argument2 in
        guard let strongContext = context, let strongContext2 = context2 else { return }
        closure(strongContext, strongContext2, argument1, argument2)
    }
}

Basically we generate a variant that takes N context arguments and N original function arguments and strongify them. A little boilerplate to remove a lot of boilerplate from your call-sites.

I put a 1-file µframework on my GitHub repo.

Support my writing: PayPal Tip Jar or become a Patron