Refactoring in Swift: Closure Callbacks

When using closures, the contents of a closure often contains a single line: a call to a function that performs work with the arguments. This takes three lines of code, which, for what it does, is two too many.

Yes, we’ll be talking about currying, but not with these ingredients…

Have you ever seen this before, perhaps in a viewDidLoad ?

self.model.intakeValueChanged = { [weak self] value in

self?.updateIntake(to: value)

}



self.model.calorieValueChanged = { [weak self] calories in

self?.updateCalories(to: calories)

}



self.model.dataIsValid = { [weak self] valid in

self?.updateSaveButton(active: valid)

}



self.model.saveActionCompleted = { [weak self] in

self?.resetData()

}

While this isn’t terrible, all I can see is a repeated pattern that would be matched by this regular expression. And what do we do when we see repeated patterns in our code? We refactor.

Out of the box, Swift allows you to assign a function to a closure property. We could therefore refactor by doing just that:

self.model.intakeValueChanged = self.updateIntake(to:) self.model.calorieValueChanged = self.updateCalories(to:) self.model.dataIsValid = self.updateSaveButton(active:) self.model.saveActionCompleted = self.resetData

This is legal, however alarm bells should be ringing in your head: what about our weak ?

Whilst we haven’t needed to worry about manually counting references since ARC, it’s still important to keep in mind what’s going on in the background to ensure retain cycles aren’t created.

In the first example, calling print(CFGetRetainCount(self)) after the code is run results in 7 being printed to the console, however calling it from the bottom of the second example prints 8 . We’ve created a retain cycle.

Enter what my colleague told me was called “function currying” and I didn’t believe him so I googled it (this was a few years ago…). In this case, currying means we can convert our instance function into a function that takes the an instance as an argument and returns the original function.

self.someFunc(arg:) == type(of: self).someFunc(self)

For example, imagine our updateIntake(to:) function lives inside of ViewController :

class ViewController: UIViewController {



func updateIntake(to value: Int) {

...

}

}

To call updateIntake(to:) from within ViewController , we would normally simply use self.updateIntake(to: someInt) . However the “curried” way of calling this is:

let function: ((Int) -> ()) = ViewController.updateIntake(self)

function(someInt)

This works because the static-looking version of updateIntake(to:) takes an instance of ViewController and returns a function that is the same as it’s instance version. I’ve added the type declaration in the above snippet for clarity only — all you need is ViewController.updateIntake(self)(someInt) .

So, a simple function can be used to map between instance functions and closures without increasing self ’s reference count. Here’s a version that works for functions with one argument and no return value:

protocol Bindable: class {}

extension Bindable {



typealias Function = Self



func weak<Args>(_ method: @escaping ((Function) -> ((Args) -> Void))) -> ((Args) -> Void) {

return { [weak self] arg in

guard let `self` = self else { return }

method(self)(arg)

}

}

}

To make it more reusable, I’ve defined a typealias called Function that simply maps to the name of the class. Make your class conform to Bindable , and you’re ready to go:

self.model.intakeValueChanged = weak(Function.updateIntake(to:)) self.model.calorieValueChanged = weak(Function.updateCalories(to:)) self.model.dataIsValid = weak(Function.updateSaveButton(active:)) self.model.saveActionCompleted = weak(Function.resetData)

You’ll need to add another function without Args to the Bindable in order to support that last line as saveActionCompleted / resetData doesn’t have any arguments. You could also create an unowned version for closures that have return values.