One of the things that becomes obvious very fast is that iOS uses function pointers (selectors) or delegates to do callbacks. Usually iOS only allows one, meaning one pointer or one delegate. This is fine if you are the model to a table view or something, but in the case of events, history (read: Java) has proven that the listener approach is very effective. Since I am coding a number of components for iOS now, I really would like them to inform interested objects of what happened. For example when a date was picked in the calendar picker, or a year changed in the year mini picker. And I’m not going to code that again and again. Hence: KPListenerManager.
The usage is fairly simple (taking the calendar picker as the example):
1. include an instance of this class as a public property in your header file and synthesize it in the module file
@property (readonly,assign) KPListenerManager* dateSelectedListeners;
2. initialize it in the init of your class
dateSelectedListeners = [[KPListenerManager alloc] init]; // released in dealloc
3. and then notify the listeners when there is something to report
[dateSelectedListeners notify:date];
And naturally the other side is registering to the manager and implementing the callback method (the arguments have to match):
[_calendarPicker.dateSelectedListeners add:self selector:@selector(dateSelected:)]; ... - (void)dateSelected:(NSDate*)date { ... }
In the usage of this class in my tiny test project, I already had the need to also have an object be send along next to what ever the class is notifying about; I called that a piggyback. It’s data that a listener object includes when it is registering, and gets added to the callback method as the last parameter. So when registering, the “piggyback:” argument holds what ever needs to be piggy backed, and the selector must add the additional argument. As an example I’ve extended the dataSelected with a piggyback:
[_calendarPicker.dateSelectedListeners add:self selector:@selector(dateSelected:mydata:) piggyback:someData]; ... - (void)dateSelected:(NSDate*)date mydata:(id)thePiggybackedData { ... }
So for your pleasure and entertainment…
KPListenerManager.h:
// // KPListenerManager.h // DH2iPad // // Created by TBEE on 07.12.10. // Copyright 2010 KnowledgePlaza. All rights reserved. // @interface KPListenerManager : NSObject { NSMutableArray* _listeners; } - (void)add:(id)listener selector:(SEL)selector; - (void)add:(id)listener selector:(SEL)selector piggyback:(id)piggyback; - (void)remove:(id)listener selector:(SEL)selector; - (void)notify; - (void)notify:(id)object; - (void)notify:(id)object and:(id)object2; @end
KPListenerManager.m:
//
// KPListenerManager.m
// DH2iPad
//
// Created by TBEE on 07.12.10.
// Copyright 2010 KnowledgePlaza. All rights reserved.
//
// A class to manage listeners with.
// Usage is a follows:
//
// 1. create an instance of this class as a public variable in your .h
// KPListenerManager* dateSelectedListeners;
//
// 2. initialize in the init
// dateSelectedListeners = [[KPListenerManager alloc] init]; // released in dealloc
//
// 3. notify the listeners when it is time
// [dateSelectedListeners notify:date];
//
// And naturally the other side is registering to the listeners manager:
// [_calendarPicker.dateSelectedListeners add:self selector:@selector(dateSelected:)];
// …
// – (void)dateSelected:(NSDate*)date {
// …
// }
//
#import “KPListenerManager.h”
@implementation KPListenerManager
-(id)init
{
if (self = [super init])
{
// listeners
_listeners = [[NSMutableArray alloc] init]; // released in dealloc
}
return self;
}
– (void)dealloc {
[_listeners release]; // also releases it contents
[super dealloc];
}
// add a listener
– (void)add:(id)listener selector:(SEL)selector {
[self add:listener selector:selector piggyback:nil];
}
// add a listener and a piggyback that must be send as the last parameter
– (void)add:(id)listener selector:(SEL)selector piggyback:(id)piggyback {
// create a dictionary to store the listener and selector in
NSMutableDictionary *lDictionary = [[NSMutableDictionary alloc] init]; // we alloc, we must release
[lDictionary setObject:listener forKey:@”listener”]; // we need to hang on to listener, but the dictionary takes care of that for us
[lDictionary setObject:[NSValue valueWithPointer:selector] forKey:@”selector”]; // we need to hang on to selector, but the dictionary takes care of that for us
if (piggyback != nil) [lDictionary setObject:piggyback forKey:@”piggyback”]; // we need to hang on to piggyback, but the dictionary takes care of that for us
// store the dictionary
[_listeners addObject:lDictionary]; // addObject does a retain, so
[lDictionary release]; // we can release
}
– (void)remove:(id)listener selector:(SEL)selector {
// loop over all listeners
for (int i = 0; i < [_listeners count]; i++) {
// get the listener
NSMutableDictionary *lDictionary = [_listeners objectAtIndex:i];
id lListener = [lDictionary objectForKey:@"listener"];
SEL lSelector = [[lDictionary objectForKey:@"selector"] pointerValue];
// compare
if ((listener == lListener) && (selector == lSelector)) {
// remove
[_listeners removeObjectAtIndex:i];
return;
}
}
}
// call all listeners (expects a method with no arguments or just the piggyback)
– (void)notify {
// loop over all listeners
for (int i = 0; i < [_listeners count]; i++) {
// get the listener
NSMutableDictionary *lDictionary = [_listeners objectAtIndex:i];
id lListener = [lDictionary objectForKey:@"listener"];
SEL lSelector = [[lDictionary objectForKey:@"selector"] pointerValue];
id lPiggyback = [lDictionary objectForKey:@"piggyback"];
// execute
if (lPiggyback == nil) [lListener performSelector:lSelector];
else [lListener performSelector:lSelector withObject:lPiggyback];
}
}
// call all listeners (expects a method with one arguments and optionally the piggyback)
– (void)notify:(id)object {
// loop over all listeners
for (int i = 0; i < [_listeners count]; i++) {
// get the listener
NSMutableDictionary *lDictionary = [_listeners objectAtIndex:i];
id lListener = [lDictionary objectForKey:@"listener"];
SEL lSelector = [[lDictionary objectForKey:@"selector"] pointerValue];
id lPiggyback = [lDictionary objectForKey:@"piggyback"];
// execute
if (lPiggyback == nil) [lListener performSelector:lSelector withObject:object];
else [lListener performSelector:lSelector withObject:object withObject:lPiggyback];
}
}
// call all listeners (expects a method with matching arguments and optionally the piggyback)
– (void)notify:(id)object and:(id)object2 {
// loop over all listeners
for (int i = 0; i < [_listeners count]; i++) {
// get the listener
NSMutableDictionary *lDictionary = [_listeners objectAtIndex:i];
id lListener = [lDictionary objectForKey:@"listener"];
SEL lSelector = [[lDictionary objectForKey:@"selector"] pointerValue];
id lPiggyback = [lDictionary objectForKey:@"piggyback"];
// execute (switched to NSInvocation because we have 3 arguments now)
NSInvocation *lNSInvocation = [NSInvocation invocationWithMethodSignature: [lListener methodSignatureForSelector:lSelector]];
[lNSInvocation setTarget:lListener];
[lNSInvocation setSelector:lSelector];
// Note: Indexes 0 and 1 correspond to the implicit arguments self and _cmd, which are set using setTarget and setSelector.
[lNSInvocation setArgument:&object atIndex:2];
[lNSInvocation setArgument:&object2 atIndex:3];
if (lPiggyback != nil) [lNSInvocation setArgument:object atIndex:4];
[lNSInvocation invoke];
}
}
// call all listeners (expects a method with matching arguments and optionally the piggyback)
– (void)notify:(id)object and:(id)object2 and:(id)object3 {
// loop over all listeners
for (int i = 0; i < [_listeners count]; i++) {
// get the listener
NSMutableDictionary *lDictionary = [_listeners objectAtIndex:i];
id lListener = [lDictionary objectForKey:@"listener"];
SEL lSelector = [[lDictionary objectForKey:@"selector"] pointerValue];
id lPiggyback = [lDictionary objectForKey:@"piggyback"];
// execute (switched to NSInvocation because we have 3 arguments now)
NSInvocation *lNSInvocation = [NSInvocation invocationWithMethodSignature: [lListener methodSignatureForSelector:lSelector]];
[lNSInvocation setTarget:lListener];
[lNSInvocation setSelector:lSelector];
// Note: Indexes 0 and 1 correspond to the implicit arguments self and _cmd, which are set using setTarget and setSelector.
[lNSInvocation setArgument:&object atIndex:2];
[lNSInvocation setArgument:&object2 atIndex:3];
[lNSInvocation setArgument:&object3 atIndex:4];
if (lPiggyback != nil) [lNSInvocation setArgument:object atIndex:5];
[lNSInvocation invoke];
}
}
@end
[/sourcecode]
Indeed, the actual code contained &’s. Thanks for pointing that out. I updated the code.
That would be interesting, since this is actually running code. I’ll go out and check if I made any changes to it after posting this blog.
You are using NSInvocation wrong. That should be
[lNSInvocation setArgument:&object atIndex:2];
[lNSInvocation setArgument:&object2 atIndex:3];