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:
- Fetch the associateDelegate from the object
- Add delegate methods to the delegate using blocks
- Set the objects delegate to the associate delegate
//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;
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,
_cmd). The block implementation only requires
self. However, we cannot call the argument
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!)