Update: These nifty utility functions are now available as the Outlets Pod.
This post is a eulogy to the syntactic-sugar syntax for curried functions in Swift 1-2.
A couple years ago I built a universal iPhone / iPad app with three storyboards.
- shared scenes
Many of the view controllers between the device-specific storyboards were shared and thus the outlets and actions all had to be the same. Each outlet property was bound to two different storyboards and required launching the app in different simulators to manually validate they were hooked up correctly. It was a constant challenge to keep them in sync whenever renaming a property or method. Most of the time I would forget to update at least one outlet and I’d have another lovely crasher from the device I forgot to test on.
I came up with a scheme to use unit tests to assert that these outlets and actions were bound correctly so that I could validate them almost instantly. I ended up with a handful of ugly C functions that I never shared.
It’s much easier nowadays to build a universal iPhone/iPad app with shared storyboards due to Size Classes. The new UI Testing in Xcode 7 makes it much easier to automate testing, which can catch any missed outlet/action bindings (as long as your test touches every UI element). However, I still find this sort of low-level assertion helpful, especially since it’s so easy to do.
Swift Curried Functions
When I learned that Swift had super-clean function currying syntax I refactored these ugly helper functions into something much more beautiful, learning about a new language feature in the process.
Swift 2 has syntactic sugar for defining curried functions.
sum function can be called in a number of ways:
In this example, the
sumA function has captured the value of the
A parameter (1), whereas
sumAB has captured the values of both the
B parameters (1 and 2). When the final
C parameter is passed to any of these functions the resulting
Int value (6) is returned.
sum function using the more verbose curried function syntax shows that it is really a set of nested functions.
This more verbose syntax can get noisy very quickly with many arguments. It’s a common practice to define a
curry function which transforms a multi-parameter function into its curried version. 1
The thoughtbot Curry library has all the variations of the
curry function up to 19 parameters.
Currying helps to simplify these outlet and action test functions so that the view controller doesn’t have to be passed in every function call. Just like the
sumA function holds onto the value of the first parameter, these test functions hold onto a reference to the view controller being tested. There’s also the benefit of being able to give the returned function a very readable name.
Just look at how beautiful this is!
This is a BDD-style test using the Quick framework.
So, what is that
hasButtonOutlet magic? It’s a partially-applied function saved in a local variable. This is how it is created:
The return type in the
hasButtonOutlet declaration (
UIButton in the above example) is the type used for validation inside the generic
outlet function below.
outlet full function would look like this:
But, you might as well just have a plain, old multi-parameter function if you want all that. Currying reduces noise and makes these tests more readable - Handy when you have dozens of outlets and are chasing down which one you mistyped.
Here is a condensed definition of the
outlet curried function:
failfunction is part of the Nimble matcher framework
The action assertion functions are similarly simple.
One caveat is that they require an outlet on the thing sending the action. A lot of the time an outlet isn’t necessary for an action-sending UI element, but I haven’t found a way to get the actions from the view controller (yet).
Here is the setup for the partially-applied
Implementation of the
action function is more complex as getting to the IBAction differs depending on whether the UI element is a
UIBarButtonItem or a type of
expectfunction is part of the Nimble matcher framework
A full project demonstrating these helper functions is available at:
The functions in the sample code are much more beautiful due to @esttorhe’s help in simplifying the API.
Running the tests in the example project gives quick3 feedback that all the outlets and actions are properly connected without even launching the app.
Shortly after @allonsykraken posted Hipster Swift, I learned that the super-clean syntactic sugar version of curried functions is going away in Swift 34 and it made me sad. While this is a more esoteric language feature, I really like how curried functions can be used to simplify an API. Also, the way Swift implemented curried functions made them so easy to use.
…so much cleaner than this? 5
Versions of these outlet/action assertion functions using the older, cleaner syntactic-sugary function currying can be reviewed on the
deprecated-syntax tag of the example repo.
Apple, you can take my sweet curry, but you’ll never take my Sriracha.
Note that function currying isn’t going away, only the nice shorthand (sweet ) syntax is. ↩