Search
Rich's Mad Rants
Powered by Squarespace

Entries in Blocks (2)

Sunday
May272012

RWAlertManager: a Ruby-inspired, fluent, block-based wrapper for UIAlertView

The Problem

I've always hated the way UIAlertView works. Oh, it's easy enough to display an alert. But, if you want to respond to any button presses, you have to filter everything through a delegate method. Besides the extra typing, and jumping back and forth to the header file, this also means I'm spreading my alert logic about. This is particularly bad, since I typically display an alert messages to the user to get a quick "A or B" answer. My code would be a whole lot cleaner if I could just deal with the button presses locally.

And things get even worse if you are creating more than one alert in the same class. Then all the alerts get filtered through the same delegate method--and you will need to separate them before responding to the button presses.

For a while now, I've thought that we really need a clean block-based API for alerts. That would let me define both my alert and the responses to the button presses locally. No more need for a delegate. Everything could be nice, clean and simple.

I had in mind something that would work like this:

RWAlertManager *manager = [RWAlertManager sharedAlertManager];

[manager createAlertWithTitle:@"Some Random Error"
                      message:@"Do you want me to delete all the user's data?"];

 
[manager addButtonWithTitle:@"Yes"
                   andBlock:^{ [self deleteEverything]; }];

[manager addButtonWithTitle:@"No"
                   andBlock:^{ [self saveDataAndExit]; }];

[manager showAlert];

There are some problems with this. For example, we need to call createAlertWithTitle:message: first. Then we need to call all our addButtonWithTitle:andBlock: calls. Finally we need to call showAlert. Bad things will probably happen if we get these out of order. And we'd probably need some rather messy code behind the scenes to track everything.

Still, to my mind, that would be a huge improvement over the current UIAlertView API.

However, as many of you know, I've been experimenting with the new RubyMotion SDK recently. So far, I'm very impressed with it. Yes, there are a few things that I'd like to see implemented before I try using it for a real-world project. Fortunately, HipByte seems to be developing at a breakneck speed, so I'm sure those gaps will be filled in soon.

This also means I've had Ruby on the brain lately. One of the things I dearly love about Ruby is the ease with which I can pull off really crafty metaprogramming feats (basically writing code that writes code), or create internal Domain Specific Languages (DSLs).

So, this got me to thinking. Both Ruby and Objective-C are highly-dynamic, object-oriented, message passing languages. They are both heavily influenced by Smalltalk. Why can't I use the same metaprogramming tricks I'd use in Ruby to create an even slicker interface here. Maybe not something you'd call a full-fledged DSL, but at least a fluent API.

Admittedly, Objective-C is a much more ceremonial language (requiring a lot more typing and boilerplate than Ruby). Also, its syntax isn't nearly as flexible as Ruby. And the block syntax, frankly, sucks. So, I can probably only push this so far. Still, I thought it was worth a try.

The Solution

I decided to create an API, that would let me show an alert with the following code:

[[RWAlertManager sharedAlertManager]
 showAlertWithTitle:@"Some Random Error"
 message:@"Do you want to delete all the user's data?"
 configurationBlock:^(AddButtonBlock addButton) {
                 
     addButton(@"YES", ^{ [self deleteEverything]; });
     addButton(@"NO",  ^{ [self saveDataAndExit]; });
}];

OK, hold on to your hatters. We're about to go down the rabbit hole.

Here's the basics. showAlertWithTitle:message:configurationBlock: will create an alert view, then pass a block back which I can use to configure the view. Once I'm done, it will automatically display the view.

Inside the configuration block, the system hands me an AddButtonBlock. We can ignore the fact that this is a block--instead think of it as a function that we call to add buttons to our view. However, this function is only accessible inside our configuration block.

Note: This is a great example of encapsulation. Unlike my first proposed solution, we cannot call addButton at the wrong time. It is only accessible inside my configuration block. More importantly, this type of encapsulation falls outside the normal object/instance variable/method encapsulation we're accustomed to in Objective-C--though it's the type of thing I do all the time in Ruby or Lisp.

The addButton block (function) takes two arguments, a string (the button's title) and a block (the action that will occur when that button is pressed). If we don't want any actions to occur beyond simply dismissing the alert, we can pass in nil.

So, basically we have a block inside a block inside a block. Confused yet? Don't worry. It's not that bad.

The Implementation

RWAlertManager is a singleton. It will use two dictionaries. One will store the alert views (just in case we have more than one alert active at a time). The other will be a dictionary containing arrays of blocks--one array for each alert view, with one block for each button.

We'll auto-generate locally-unique keys for our views, and use them to access both dictionaries. The code for all of this is fairly straightforward, so I won't go into it here. You can check it out in the source code.

The real work starts with showAlertWithTitle:message:configurationBlock:.

- (void)showAlertWithTitle:(NSString *)title 
                   message:(NSString *)message 
        configurationBlock:(void (^)(AddButtonBlock addButton))config {
    
    // Setup
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
                                                    message:message
                                                   delegate:self
                                          cancelButtonTitle:nil
                                          otherButtonTitles:nil];
    
    RWAlertID *key = [self keyForAlertView:alert];
    NSMutableArray *blockList = [NSMutableArray array];
    
    // Build the config blocks
    AddButtonBlock addButton = [self createAddButtonBlockForAlertView:alert
                                                         andBlockList:blockList];
    
    // Call the config block
    if (config != nil) config(addButton);
    
    // Save Data
    [self.alerts setObject:alert forKey:key];
    [self.blockLists setObject:blockList forKey:key];
    
    // Display the view
    [alert show];
    
    
    // if it doesn't have any buttons, autodismiss it
    if ([blockList count] == 0) {
        
        [self autoDismissAlertView:alert];
    }
    
}

I start by creating a UIAlertView with no buttons, setting self as the delegate. Then I generate my key, and create a mutable array to hold my list of blocks.

Next, I call createAddButtonBlockForAlertView:andBlockList: to create my add button block--we'll cover that in a second.

Then, if I have a configuration block, I call it, passing the addButton block.

Finally, I save the alert and the blacklist, and display the alert. If my alert doesn't have any buttons, I call autoDismissAlertView: to automatically dismiss the alert after a few seconds.

Buttonless alerts aren't very pretty--there's a bit too much space at the bottom for my tastes. Still, sine you can use the API to create a buttonless alert, we'd better provide reasonable behaviors for handling them.

AddButtonBlock is defined in the header file as:

typedef void (^ResponseBlock) (void);
typedef void (^AddButtonBlock) (NSString *title, ResponseBlock response);

Typdeffing blocks is one of the ways to make them a little less onerous in Objective-C.

OK, nothing too shocking there. Let's look at createAddButtonBlockForAlertView:andBlockList:.

- (AddButtonBlock)createAddButtonBlockForAlertView:(UIAlertView *)alert 
                                      andBlockList:(NSMutableArray *)blockList {
    
    return ^(NSString *title, ResponseBlock block){
        
        [alert addButtonWithTitle:title];
        
        if (block != nil) {
            
            [blockList addObject:block];
            
        } else {
            
            [blockList addObject:^{}];
            
        }
    };
}

First off, I know. I screwed up the naming. We're not supposed to use "and" when introducing additional parameters in a method. I'll fix that when I refactor the code.

Moving on...I create a block that consumes a string and a ResponseBlock. We've already seen the typedef for ResponseBlock. It's basically a block that doesn't take any arguments, and which doesn't return any values.

The block I'm creating will capture the alert and blockList variables, letting me access them inside my newly created block. Since these are objects, my block will retain them (if necessary), and I can actually call their methods (thus modify them).

When this new block is run, it will add a new button to the alert, using the provided title. Then it will add the provided ResponseBlock to the block list. If you pass nil for the response block, it will simply create a null-op block and add that instead. That way there's always a valid block for each button (even if the block doesn't do anything).

This is one of the few places where Objective-C's "you can safely call any methods on nil" attitude can get you in trouble. Calling a null-valued block will crash your app.

Now we need to respond to button presses. Let's look at the UIAlertViewDelegate method alertView:willDismissWithButtonIndex:.

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
    
    RWAlertID *key = [self keyForAlertView:alertView];
    [self processButtonPress:buttonIndex forAlertKey:key];

    // Now remove the alert and it's blocks -- deleting them from memory
    [self.alerts removeObjectForKey:key];
    [self.blockLists removeObjectForKey:key];
    
}

Here we simply get the key for the alert view. Then we call processButtonPress:forAlertKey: to handle dispatching to the correct block. We'll look at that code next. Finally we remove the alert and block list from their respective dictionaries. This means we will no longer hold any references to them, and they will be deallocated from memory. If you haven't guessed yet, I'm using ARC here. However, I think the Manually Managed Memory version would be nearly identical (with some additional complexity for moving blocks into the heap).

processButtonPress:forAlertKey: is almost as simple:

-(void)processButtonPress:(NSInteger)buttonIndex forAlertKey:(RWAlertID *)key {
    
    // Ignore negative indicies
    if (buttonIndex < 0) return;
    
    NSArray *blocks = [self.blockLists objectForKey:key];
    ResponseBlock block = [blocks objectAtIndex:buttonIndex];
    
    block();
}

If the button index is less than 0 we simply return. We'll use this to dismiss buttonless alerts. Next, we use our key to get the array of blocks from the block dictionary. Then we use the button index to get the correct block. Finally, we fire off the block.

Last, and probably least, we have autoDismissAlertView:

- (void)autoDismissAlertView:(UIAlertView *)alert
{
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = 
    dispatch_time(DISPATCH_TIME_NOW, 
                  (long long)(delayInSeconds * NSEC_PER_SEC));
    
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        
        [alert dismissWithClickedButtonIndex:-1 animated:YES];
        
    });
}

Here, we simply use GDC to dispatch a block two seconds in the future. Inside this block, we call dismissWithClickedButtonIndex:animated: and pass -1 as the button index.

And that's essentially it. Yes, we end up three-layers deep in blocks--but it's not really that complicated when you examine all the pieces. Each step is, in itself, simple enough.

Let me know what you think, or if you have any questions or comments. You can grab the complete source code, including a sample harness that triggers different types of alerts here.

-Rich-
Sunday
Mar112012

Fun with Blocks

In last week's class when we were talking about blocks, one of the students asked if any methods ever took block arguments with return values. I couldn't think of any API calls off hand--but I also couldn't think of any reasons why it wouldn't be possible. In fact, I realized it could be used to easily map from one array to another.

So I created a quick proof-of-concept, using a category on NSArray with a single method:

 

- (NSArray*)mapUsingBlock:(id (^) (id object)) block {

    NSMutableArray* newArray = [NSMutableArray arrayWithCapacity:[self count]];

     for (id object in self) {
         [newArray addObject:block(object)];
     }

    return [newArray copy];
}

 

This simply iterates over the array and passes each item from our array to our block. It then stores all the return values in a new array, which it returns.

I could then call the code as shown below:

 

NSArray* names = [NSArray arrayWithObjects: @"Bob", @"Sally", @"Mary", @"Jim", nil];

 

NSArray* allCaps = [names mapUsingBlock:(id)^(id object) {

 

    return [object uppercaseString];

}];

 

NSLog(@"Names = %@", names);

NSLog(@"All Caps = %@", allCaps);

 

NSArray* pigLatin = [names mapUsingBlock:^id(id object) {

 

    NSString* firstCharacter = [[object substringToIndex:1] lowercaseString];

    NSString* restOfName = [[object substringFromIndex:1] capitalizedString];

 

    return [NSString stringWithFormat:@"%@%@ay", restOfName, firstCharacter];

}];

 

NSLog(@"Pig Latin: %@", pigLatin);

 

 

NSArray* counts = [names mapUsingBlock:^id(id object) {

 

    return [NSNumber numberWithInt:[object length]];

}];

 

NSLog(@"Counts: %@", counts);


id (^booBlock) (id object) = ^id(id object) {return @"Boo";};

NSArray* boo = [names mapUsingBlock:booBlock];

 

NSLog(@"Boo: %@", boo);


The first example block simply returns an all-caps version of its argument. The second somewhat naively converts its argument into pig latin. The third returns an NSNumber holding the length of its argument--showing that the new array does not need to contain the same type of object as the original array. Finally, the last block simply returns a string value--it doesn't use the block's argument at all.

These examples also shows a few different ways of declaring our block. The first three define the block in-line. The last example stores the block in a local variable before passing it into our method.

Calling this code generates the following output:

 

2012-03-11 00:11:17.841 ArrayMap[13029:403] Names = (

    Bob,

    Sally,

    Mary,

    Jim

)

2012-03-11 00:11:17.844 ArrayMap[13029:403] All Caps = (

    BOB,

    SALLY,

    MARY,

    JIM

)

2012-03-11 00:11:17.846 ArrayMap[13029:403] Pig Latin: (

    Obbay,

    Allysay,

    Arymay,

    Imjay

)

2012-03-11 00:11:17.848 ArrayMap[13029:403] Counts: (

    3,

    5,

    4,

    3

)

2012-03-11 00:11:17.848 ArrayMap[13029:403] Boo: (

    Boo,

    Boo,

    Boo,

    Boo

)


So far, so good. But, let's look at Objective-C's block syntax for a second. First off, when calling mapUsingBlock: there are (at least) two valid syntaxes for creating our block: (id)^(id object) or ^id(id object). Neither of these match the syntax used when defining the method: (id (^) (id object)) block. Additionally, all three of these are different from the syntax used to declare local block variables: id (^blockName) (id object).

Dont' get me wrong. I love blocks. Of course, I was first exposed to them (or lambdas, closures, or whatever name you may choose to call these constructs) in languages like Lisp, Smalltalk and Ruby. I think they're a great addition to Objective-C, and I generally prefer Objective-C APIs that use blocks. I mean, seriously. Could we get a block-based API for UIAlertView? That would be awesome.

I even find myself writing my own methods to consume blocks. And not just toy examples like this. I'm all in on this whole block thing--and it looks like Apple is too. This became especially clear with iOS 5. There are a number of features (iCloud and Twitter come to mind) that you just cannot use without blocks.

Having said all that, let's face the facts. The block syntax is ugly and inconsistent, and it often drives me crazy. I find it almost impossible to remember the correct syntax from one use to the next. The only saving grace is Xcode's autocompletion. It generally does a great job creating the proper syntax for block arguments when calling block-based methods. Which helps a lot when using block-based APIs. Still, if you start creating your own block-based methods, you're on your own.