We're hiring!

We're actively seeking developers and designers for our Ann Arbor & Detroit locations.

Replacing the Objective-C “Delegate Pattern” with ReactiveCocoa

ReactiveCocoa is a library created by GitHub that brings “Functional Reactive Programming” to Objective-C. It provides a number useful features right out of the gate which gives a developer the power to observe, transform, merge, and filter signals that contain values.

More importantly, it has the power to build on top of and replace existing (and sometimes archaic) patterns used in Objective-C. One of the most common of these patterns is the “delegate pattern.”

The Delegate Pattern

The delegate pattern is a pattern of delegating tasks to an object and potentially informing it of certain actions that are being taken. Here’s a typical example of the delegate pattern found in iOS programming:

- (void)viewDidLoad {
    UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame: CGRectZero];
    self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
    self.searchController.delegate = self;
    // Place it in view
    searchBar.delegate = self;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
    self.searchResults = [self search: text];
}

- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)searchController {
    self.isSearching = YES;
}

- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)searchController {
    self.isSearching = NO;
}

In this particular example, the object (presumably a UIViewController) is the delegate to both the UISearchBar and UISearchDisplayController. The searchBar:textDidChange: delegate method updates the search results and the UISearchDisplayController delegate methods (searchDisplayControllerDidBeginSearch and searchDisplayControllerDidEndSearch respectively) track whether the search is currently active.

There are a couple of issues with this delegate approach:

  1. There is only ever one object that can subscribe to changes to the UISearchBar and UISearchDisplayController. All actions must go through the delegate.
  2. The act of searching is is scattered across three different callbacks.

The ReactiveCocoa Toolbox

ReactiveCocoa has built-in helpers for creating signals from any selector on an object or protocol.

rac_signalForSelector: and rac_signalForSelector:fromProtocol:

These two helpers will create a signal that is bound to a selector, such that any time that selector is invoked on the object it will send a new value through the signal.

rac_liftSelector:withSignals:

This helper will “lift” a selector into “signal space” where the selector is invoked anytime the corresponding argument signals send a new value. The signals passed to the rac_liftSelector must match the number of arguments expressed by the selector.

Replacing the Delegate Pattern

Now that we have familiarized ourselves with some of the more powerful aspects of ReactiveCocoa, we can replace the delegate pattern example with one that uses signals.

UISearchBar

Instead of having to assign a delegate to the UISearchBar and implement searchBar:textDidChange:, let’s modify the UISearchBar so there is a signal representing changes to the text.

@implementation UISearchBar (RAC)
- (RACSignal *)rac_textSignal {
    self.delegate = self;
    RACSignal *signal = objc_getAssociatedObject(self, _cmd);
    if (signal != nil) return signal;
    
    /* Create signal from selector */
    signal = [[self rac_signalForSelector:@selector(searchBar:textDidChange:) 
                    fromProtocol:@protocol(UISearchBarDelegate)] map:^id(RACTuple *tuple) {
        return tuple.second;
    }];
    
    objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return signal;
}
@end

A new method had been added to UISearchBar (via Objective-C categories) that creates a signal from the selector searchBar:textDidChange: and maps the signal so that it only ever sends the text.

UISearchDisplayController

In order to know whether the UISearchDisplayController is active or not, it requires that two delegate selectors are subscribed to: searchDisplayControllerDidBeginSearch: and searchDisplayControllerDidEndSearch:

@implementation UISearchDisplayController (RAC)
- (RACSignal *)rac_isActiveSignal {
    self.delegate = self;
    RACSignal *signal = objc_getAssociatedObject(self, _cmd);
    if (signal != nil) return signal;
    
    /* Create two signals and merge them */
    RACSignal *didBeginEditing = [[self rac_signalForSelector:@selector(searchDisplayControllerDidBeginSearch:) 
                                        fromProtocol:@protocol(UISearchDisplayDelegate)] mapReplace:@YES];
    RACSignal *didEndEditing = [[self rac_signalForSelector:@selector(searchDisplayControllerDidEndSearch:) 
                                      fromProtocol:@protocol(UISearchDisplayDelegate)] mapReplace:@NO];
    signal = [RACSignal merge:@[didBeginEditing, didEndEditing]];
    
    
    objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return signal;
}
@end

The implementation above does the following:

  1. Creates a signal from searchDisplayControllerDidBeginSearch: and maps it to YES.
  2. Creates a signal from searchDisplayControllerDidEndSearch and maps it to NO.
  3. Merges the two signals via RACSignal’s merge method where it sends the most recent signal value.

Bringing it All Together

We can now replace the delegate pattern example above with this:

- (void)viewDidLoad {
  UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame: CGRectZero];
  self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
  RAC(self, searchResults) = [self rac_liftSelector:@selector(search:) withSignals:self.searchBar.rac_textSignal, nil];
  RAC(self, searching) = [[self.searchController rac_isActiveSignal] doNext:^(id x) {
      NSLog(@"Searching %@", x);
  }];
}

The search results are updated when the search bar’s text changes, and the search is marked as active when the search display controller is active. Moreover, if another object wanted to subscribe for changes, it wouldn’t have to go through a delegate — it could simply subscribe to the signal.

An example iOS project demonstrating the example’s shown above can be found at my Github accout.
 

Justin DeWind (45 Posts)

This entry was posted in Objective-C and tagged , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

11 Comments

  1. Chris Rittersdorf
    Posted February 3, 2014 at 4:05 pm

    Would you have any interest in giving a talk about ReactiveCocoa at the GR CocoaHeads? I believe there would be significant interest in this topic.

    • Justin DeWind
      Posted February 3, 2014 at 8:51 pm

      Yeah, I would certainly be interested in discussing ReactiveCocoa at GR CocoaHeads. I think it would be a great topic to discuss since I think everyone should be using it. :-)

  2. Posted February 3, 2014 at 6:12 pm

    Extremely interesting! Been trying to implement this in a generic way myself, but couldn’t really make it work in the end. I’ll settle for implementing specific RAC categories like you have.

    Didn’t know about rac_signalForSelector:fromProtocol:, thanks for highlighting it.

  3. Ruben Hansen-Rojas
    Posted February 4, 2014 at 10:34 am

    Yeah, but the delegate pattern isn’t designed to facilitate event subscription. The ReactiveCocoa implementation here is a functional drop-in replacement, yes. And it certainly does it well enough. But this is no longer a delegate pattern as intended by the UISearchDisplayDelegate protocol. Just nitpicking the use of the word “replace,” really.

    Otherwise, RAC looks pretty sweet.

    • Nick Weaver
      Posted February 12, 2014 at 4:32 pm

      I totally agree. The introduction to FRP is nice however the example chosen does not look very promising. No doubt, you can do all kinds of things with categories and objc_g/setAssociatedObject to avoid subclassing, I am yet to be convinced of the readability of this delegate pattern replacement. I even see more complexity here.

  4. Posted February 8, 2014 at 10:12 pm

    I’m sorry but all you’ve done here is replace a simple, flexible, well understood pattern (delegation) with a whole swag of complicated boilerplate for almost no benefit.

    While I understand the allure of FRP (and used it a bit when I was doing .NET) I have yet to see a compelling use case or example of it in Objective-C.

    • Justin DeWind
      Posted February 8, 2014 at 10:19 pm

      Oliver,

      I think is a cynical view of the post. The purpose of the post was to ease individuals into FRP via a simple example using a pattern every Objective-C developer has used.

      Moreover, it was an opportunity to show how to elevate selectors into signals where the opportunities for improving other, more complicated, problems are boundless.

      Also, the delegate pattern is not very flexible or simple as complexity increases.

  5. Posted February 16, 2014 at 11:43 am

    The delegate pattern is really nice, I think I’ll keep it.

  6. Sergii
    Posted February 19, 2014 at 3:53 pm

    Hi Justin, Thanks for the post.

    What about delegate methods with return values? For example:

    @protocol NSWindowDelegate
    ....
    - (BOOL)windowShouldClose:(id)sender;
    ...

    I’d like to transform this delegate method into a signal using rac_signalForSelector and specify return value?

    • Martin Hammarbrink
      Posted June 22, 2014 at 2:58 pm

      Sergii,

      To work with delegate methods with return values you have to create the delegate method manually and let it do the appropriate return value. Just found this out myself reading through the comments here.

  7. Roy Mendoza
    Posted May 9, 2014 at 2:52 pm

    Justin great post. Thanks for the concise brief explanation.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>