I’ve been doing a lot of Node.js programming recently, and Javascript / Coffeescript programming style is starting to boil over into the way I think about Objective-C. One of my favorite practices in Javascript is the concept of deferring, throttling, or debouncing method calls. There are tons of uses for this. For example, let’s say your app is updating a model object, and you want to persist that model object when the updates are complete. Unfortunately, the model is touched in several bits of code one after another, and your model gets saved to disk twice. Or three times. Or four times. All in one pass through the run loop.

It’s pretty easy to defer or delay the execution of an objective-c method using “performSelectorAfterDelay” or any number of methods. Debouncing—running a method just once in the next pass through the run loop after it’s been called multiple times—is a bit trickier. However, in the case described above it’s perfect. Touch the model all you want, call “save” a dozen times, and in the next pass through the run loop, it get’s saved just once.

Here’s how I implemented a new debounce method “performSelectorOnMainThreadOnce”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@implementation NSObject (AssociationsAndDispatch)
 
- (void)associateValue:(id)value withKey:(void *)key
{
	objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN);
}
 
- (void)weaklyAssociateValue:(id)value withKey:(void *)key
{
	objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN);
}
 
- (id)associatedValueForKey:(void *)key
{
	return objc_getAssociatedObject(self, key);
}
 
- (void)performSelectorOnMainThreadOnce:(SEL)selector
{
    [self associateValue:[NSNumber numberWithBool: YES] withKey: (void*)selector];
 
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self associatedValueForKey: (void*)selector]) {
            [self performSelector: selector];
            [self associateValue:nil withKey:(void*)selector];
        }
    });
}
 
@end

A more advanced version of this method would allow you to say “perform this selector just once in the next 100 msec” rather than performing it in the next iteration through the run loop. Anybody want to take a stab at that?