Friday, July 22, 2011

Implementing Continuations in iOS Using Blocks

Came up with a neat use for blocks in iOS at my day job today: continuations!

Like this: I'm working on a native iOS app that occasionally shows data from a Rails website in a UIWebView. When it does so, it has to make sure the user of the iOS app is authenticated to the web app. This way we should never have to see the web app's login page in the native app, which has its own login screen.

Of course, all of this reeks of asynchronicity. Which usually means callbacks. Which usually means implementing one or twelve protocols. We've tried to encapsulate all of the authentication logic in a custom class that's used by the controller that presents the UIWebViews, but we've also recently come up with a need for multiple controllers, controlling multiple web views. And the code is already (ahem) not as attractive as it could be.

Our original workflow looks something like this:

* Render a UIWebView and invoke its loadRequest: method with some URL.
* Have the controller implement the UIWebViewDelegate protocol.
** In particular, implement webView:shouldStartLoadWithRequest:navigationType:.
** Ask the authenticator object if we need to refresh authentication.
*** If not, just load the request (happy path).
*** If so, tell the authenticator object to refresh authentication and return NO.
*** This may require collecting new authentication credentials from the user, which means another controller and view (and asynchronous request cycle) entirely.
*** It certainly requires a separate asynchronous call to the web app, to verify authentication and set the timeout for this session.
*** Oh yeah -- we need to have remembered the original request and restart it when the dust settles.
* Insanity ensues, in the form of delegates with delegates and very hard-to-follow callback trails and state management.

Enter: blocks.

With blocks we can streamline, thusly (starting with webView:shouldStartLoadWithRequest:...):

* Tell the authenticator object to make sure we're authenticated, passing in a block to execute once we are.
** This block closes over all the state we need to "do the next thing". It can refer to methods in the current controller and access the current controller's instance variables, even though it'll be executed by the authenticator object.
* If the authenticator decides we're already authenticated, it simply executes the block (the happy path).
* If we're not authenticated, but we already know our credentials, open a NSURLConnection to the web app, using the usual callbacks. When that connection finishes successfully, execute the block we were given from the original controller.
* If we're not authenticated and we need new credentials, push the login screen controller on the stack, passing *it* a block that does the NSURLConnection stuff, followed by executing the block from the original controller.
* The login controller presents a modal dialog, accepts the user's input for authentication credentials, then calls its block (the one passed from the previous step, which encapsulates "what to do next" at two levels).

When we can implement this strategy pervasively, every entity will simply ask its collaborators to do whatever they need to do, however they need to do it (including asynchronously), and know that once the task of e.g., login, is accomplished, whatever is supposed to happen next actually does.

This idea was inspired by Continuation Passing Style in compiler theory, in which every function is called with the data it needs to perform its own business, *plus* information about what to do next. When programs are expressed in this style, there is no recursion, no nesting of routines; there is only "do what you're supposed to do, then jump to the next thing". It's not a perfect analogy, but it has been surprisingly effective in making some pretty complex and ugly code easier to understand.

You're Going Down, Emacs!

Time to get serious, yo:
[david@davids-MacBook-Air-3 ~]$ cat .aliases
alias vi='emacs'
alias vim='emacs'
alias nano='emacs'
alias mate='emacs'

Saturday, July 16, 2011

iOS Pro Tip: Know Thine API!

I was reading through some code at my current project (P.S.: ThoughtWorks is hiring! Contact me if you want to submit a résumé!), when a good old-fashioned C-style for loop caught my eye. You know the kind:
for (int i = 0; i < someArray.length; i++) {
// do something with someArray[i]
}
It turns out that if someArray is an NSArray, there is API defined to help with this:
[someArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// do something with obj
// set stop to true if you want to skip the remaining objects for some reason
}];
Note: Yes, I'm aware that there is also syntax for this in Objective-C 2.0, in the form of fast enumeration. The code I was converting was specifically interested in returning the index of a particular object in the array, which is why I went with this form.

Anyway, further reading in the NSArray API led me to another fun method: indexOfObjectPassingTest. It also takes a block, which returns a boolean value indicating whether or not the current object passes the test. If it does, you get the index back and move on with your life. This turned out to be exactly what I needed to port the original bit of code to full modernity.

I also came across an example of code that was using NSString +stringWithFormat. It would create the original format string, conditionally modify it based on the presence of some optional data, then pass all available data in to format the final string. Something like this:
NSString *formatString = @"required: %@";
if (optionalData) {
formatString = [formatString stringByAppendingString:@", optional: %@"];
}
NSString *result = [NSString stringWithFormat:formatString, requiredData, optionalData];
This seemed potentially error-prone to me; I didn't like the idea of always passing in the (potentially nil) optional data, even if I was convinced there would be no placeholder for it in the format string.

A quick read through the NSString class reference led to the instance method stringByAppendingFormat. This allowed for a workflow more like the following:
NSString *result = [NSString stringWithFormat:@"required: %@", requiredData];
if (optionalData) {
result = [result stringByAppendingFormat:@", and optional:%@", optionalData];
}
This way I did not have to dance around getting the format string just right before populating it; I could tack on the extra bits on the fly, which just seems cleaner.

Sunday, July 03, 2011

iOS Pro Tip: textFieldShouldClear

If you're using UITextFieldDelegate to manage the state of a save button (e.g., to disable saving if a field is empty), you're probably implementing the textField:shouldChangeCharactersInRange:replacementString: delegate method and checking for the field being empty. This will handle the case where the user causes the text field to become empty by deleting all characters, or selecting and cutting them. In this case, you should probably also implement textFieldShouldClear:, to handle the case where the user clicks on the text field clear widget (if it exists). It turns out that action does not call the ...shouldChangeCharacters... callback.