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.