Benedict's Soapbox About Me Selected Work

20 March 2011EMKAssociateDelegate

I’ve just added a new class to my EMKPantry project on GitHub. It’s called EMKAssociateDelegate. It provides a mechanism for creating a delegate for an object and then using blocks to implement the delegate methods. It requires iOS 4.3. The use cases is when one object must act as a delegate for multiple instances of a class, but the instances require different delegate method implementations. UIAlertViews and UITextFields are the most obvious candidates.

How to use EMKAssociateDelegate

In ‘recipe’ form it works as follows:

  1. Fetch the associateDelegate from the object
  2. Add delegate methods to the delegate using blocks
  3. Set the objects delegate to the associate delegate

In code:

-(void)viewDidAppear:(BOOL)animated 
{
    [super viewDidAppear:animated];

    //0. create the object that requires a delegate     
    UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Hello" message:nil delegate:nil cancelButtonTitle:@"Bye!" otherButtonTitles:nil] autorelease];

    //1. fetch the associate delegate from the object
    EMKAssociateDelegate *delegate = [alert EMK_associateDelegate];</p>

    //2. add a delegate method to the delegate
    SEL selector = @selector(alertView:clickedButtonAtIndex:);
    const char *types = [EMKAssociateDelegate typeEncodingForMethodWithReturnType:@encode(void) argumentTypes:@encode(UIAlertView), @encode(NSInteger), NULL];     
    //note the type signature for the block. Its the delegate method but with 'id bSelf' prefixed.     
    [delegate respondToSelector:selector typeEncoding:types usingBlock:^(id bSelf, UIAlertView *alertView, NSInteger clickedButtonAtIndex)
    {          
        NSLog(@"self: %@", self); //this will be a reference to self in the current scope (in this case it's a UIViewController)
        NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));  //this will be a _cmd in the current scope (in this case it's viewDidAppear:)
        NSLog(@"bSelf: %@", bSelf); //this will be the associated delegate
        NSLog(@"alertView: %@", alertView); //this is the first standard delegate method arguments
        NSLog(@"clickedButtonAtIndex: %i", clickedButtonAtIndex);  //this is the second standard delegate method arguments
     }];

    //3. assign the object its delegate
    alert.delegate = delegate;

    [alert show]; 
}

EMKAssociateDelegate not only requires the block to use for the delegate method but also the type encoding for the method.

typeEncodingForMethodWithReturnType:argumentTypes: is a class method on NSMethodSignature for generating the type signatures for the delegate methods. Look at the code above to see how the arguments are specified.

The type signatures for the block is not identical to the delegate method. The block requires an argument of type id to be prepended (a standard Objective-C method has two hidden arguments, self and _cmd). The block implementation only requires self. However, we cannot call the argument self because self will refer to the self in the scope that the block was created.

How EMKAssociateDelegate is implemented

As mentioned earlier EMKAssociatedDelegate is iOS 4.3 only. I tried to implement this in earlier versions but had no success. In 4.3 it’s easy thanks to imp_implementationWithBlock(void *block). I only found out about this function thanks to b.bum’s post iOS 4.3: imp_implementationWithBlock(). I’ve since tried to find the documentation for these function but have drawn a blank.

The implementation is small (currently less than 100 lines) but uses a few lesser known Objective-C features. It consists of a category on NSObject and a class called EMKAssociatedDelegate.

respondToSelector:typeEncoding:usingBlock: uses imp_implementationWithBlock() and class_addMethod() to add the block as an instance method. Once the method is added the runtime takes care of the rest.

Those with a keen eye may have spotted a flaw with this approach: Given that methods can’t be added on a per instance basis – i.e. if we add an instance method to a class it affects all instances of the class – how can two separate instances use different blocks to respond to the same method? This is addressed by dynamically subclassing EMKAssociateDelegate.

EMK_associateDelegate is a category on NSObject. It does two jobs. It creates a subclass of EMKAssociateDelegate (objc_allocateClassPair() and objc_registerClassPair()) that is unique to the instance and it associates the newly created delegate with the object (objc_setAssociatedObject()).

Get the Code

You can get the code from my GitHub project called EMKPantry. (At some point I will tidy the project up and add proper documentation – I promise!)


Make a Comment

:

:



Timeline