NSInvocation
What this?
My favourite Objective-C interview question is this:
"List as many ways as you can think of for accessing an object property."
We have a number of answers, getters & setters using direct messages or dot notation, valueForKey:, performSelector, Objective-C runtime and NSInvocation.
Beginners get the first two, intermediate level devs will probably think of the next two, but I've yet to hear anyone tell me the last two.
NSInvocation doesn't stand out as an answer, so lets have a look at it and see how it works. I'm going to give an example of some light refactoring using NSInvocation in a cute solution.
Krzysztof Zabłocki's blog post on code refactoring is something I highly recommend reading. One of the tricks shown is to use an NSDictionary instead of a switch or lots of if/else statements. I really like this solution for small MVC solutions when I'm not too fussed about creating a view model, and it's something I've used in the past. Your answer is a structure that can be increased or decreased as you add or remove dictionary entries. This is a very scalable development technique and one that is superior to having to refactor switch statements every time you change something:
Let's refactor some stuff
Here's some example code of a UITableView dataSource for a table that has two rows. The first row will display an alert view, the second one will perform a segue.
This code looks pretty much like you'd see in any beginners guide to Cocoa. On the face of it while this code isn't going to win any awards you might think it's still ok and no major crimes against programming are being committed, but there are code smells here that hint at problems to come later.
The number of rows in the table is hardcoded to 2. The other two methods have to directly check the row number (again, hardcoded). What happens when the app feature changes and a third, fourth or fifth row is added, each with different functionality? If you're not thinking of refactoring you're going to end up with something like this
But now you've been told the third cell has to move to the fifth row, the fifth to the first, the first to the... You get the point. The hardcoded nature of this solution is quickly becoming a major problem. You could just change the magic numbers, but now the if statements don't even follow logically. No, you're going to have to cut and paste code everywhere.
You also have to remember change the return value of tableView:numberOfRowsInSection: every time you add a new row. And can you imagine how awful tableView:didSelectCellAtIndexPath: looks now, with five separate possible cell reactions? This is unspeakably horrible.
Refactoring babysteps
Let's go back to the original two row problem and tidy it up a little bit in the tableView:didSelectCellAtIndexPath: method.
On the face of it we haven't done anything, just moved code around. But we've done two very important things, showAlert and segue are separate methods which are now self contained. We've also reduced the number of lines of code per method, which is an important metric in terms of maintainability. Even if we were to do nothing else now we would already have made good improvements.
To be precise we should also refactor the hardcoded strings, but that isn't relevant to this blog and I'm trying to keep changes in the example to the minimum.
Refactor using collection classes
Now we can analyse the functionality of our table. In tableView:cellForRowAIndexPath: it sets a text label, in tableView:didSelectRowAtIndexPath: it calls a method. The controlling variable is the table row. We can now start our refactor using an array of NSDictionary objects that hold the text and the selector to execute per row.
By using an array of these dictionaries we have managed to completely eradicate all of our magic numbers, and we have significantly reduced the number of lines of code. Our number of table rows is the number of dictionaries in the array. The cell's text label is the value for the key "text" of the dictionary at the given index's row in the array, and the cell's action is the value of the "selector" key.
If you're asked to change the row order in the table, you change the order the dictionaries in the array. Everything else is already taken care of. Any new rows added need nothing other than a new entry in the cellInfo array and a method in the Refactor category, none of the dataSource methods have to explicitly check the indexPath's row.
What does all this have to do with NSInvocation?
There's only one problem with this solution. It doesn't work.
@selector() returns a value of type SEL, which is a primitive type. NSDictionary can only contain Objective-C objects. So we need a solution where we can create an Objective-C object that wraps the selectors for showAlert and segue.
Enter NSInvocation. NSInvocation is an Objective-C class that contains all of the information for executing a method, you specify the target, the selector and optionally other things (but forget them for the moment) in an invocation object and then invoke the object to perform the selector on the given target. So instead of trying to execute a selector we can store showAlert and segue as NSInvocation objects in the cellInfo array. First we'll need a category for NSInvocation and then we refactor the array.
And there we have it. Our table view data source methods rely on no hard coded data, everything is taken from an array and a single change in the array will be propagated throughout the table without an need to readjust switch statements or large lists of if...else... statements.
Alternatives
As an alternative to NSInvocation you could also use a block, which it turns out is also an Objective-C object, not just a standard pointer
This is also quite a neat solution, but it's more restrictive where each block in the array must have the same prototype, with NSInvocation you can call different selectors with different return types and a different list of arguments.