Sunday, May 6, 2012

Reverse Segues

UIStoryboards make transitioning between views very easy. However, segues work strictly in one direction. I have been asked several times what is the best way to send information back to the previous view controller. Unfortunately, there is no such thing as a reverse segue. In response, I have found four clean, useful patterns to create the needed behavior:
  1. Delegate
  2. Block
  3. Shared memory
  4. NSNotificationCenter
Each one has its advantages and disadvantages. I will discuss each one in turn.

Delegate

Summary

Cocoa uses delegates everywhere, so why not here too?

Code Sample

Let's say view controller X pushes view controller Y via segue. Y specifies a delegate protocol that it will call to pass the needed information. X conforms to that protocol and sets itself as Y's delegate. When Y is dismissed, it calls the delegate method.

// In view controller X
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    [[segue destinationViewController] setDelegate:self];
}

- (void)objectUpdated:(id)object {
    // Do something with the object.
}

// In view controller Y
@protocol MyDelegateProtocol
- (void)objectUpdated:(id object);
@end

- (void)viewWillDisappear:(bool)animated {
    [super viewWillDisappear:animated];

    [[self delegate] objectUpdated:[self object]];
}

Pros/Cons

Delegation is easy enough to implement. However, in this case, the delegate protocol probably has just one method. This hardly seems worth the effort to create the protocol. Because of this, I do not recommend delegates. I instead favor the next option: blocks. If you need more than one callback, a delegate may be more appropriate.

Blocks

Summary

Blocks are great for making callbacks and can easily replace any delegate.

Code Sample

Now when X pushes Y, it also gives Y a block. When Y finishes, it calls the given block and hands it the information to pass on. The block can then do whatever it needs with that information.

// In view controller X
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    [[segue destinationViewController] setBlock:^(id object) {
        // Do something with the object.
    }];
}

// In view controller Y
typedef void(^MyBlock)(id object);

- (void)viewWillDisappear:(bool)animated {
    [super viewWillDisappear:animated];

    if (self.block)
        self.block(self.object);
}

Pros/Cons

Blocks have two great advantages over delegates. First, they don't require a protocol. For convenience, you may write a one-line block typedef, but that is all. Second, blocks keep code where it is relevant, rather in a different method. For most cases, you should favor blocks.

Shared Memory

Summary

Sometimes you may push a new view so it can edit an object. In this case, you can avoid a callback entirely.

Code Sample

When pushing Y, just pass it a pointer to the object of interest. Any changes Y makes to the shared object will directly affect X.

// In view controller X
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    [[segue destinationViewController] setObject:[self object]];
}

Pros/Cons

This solution is the simplest of the four. However, it may not be suitable in some situations. One place this pattern doesn't do well is if you want view controller Y to have a Done and Cancel button. You would need to undo all the changes made to your object if the Cancel button is pressed. This could be made easier by using an NSUndoManager, but that may be overkill for what you want.

There is one particular place that this pattern is especially useful. When using Core Data with iOS 5, you may want view controller Y to have an NSManagedObjectContext that is a child of view controller X's NSManagedObjectContext. All you need to do is create the MOC in X, set the MOC's parent, and pass it on to Y.

NSNotificationCenter

Summary

Using NSNotificationCenter to pass around information is the least used option and the most work, but it can have some nice advantages worth mentioning.

Code Sample

Rather than have X tell Y it is interested in its information, X lets NSNotificationCenter know it is interested in an event.

// In view controller X
- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                                                          selector:@selector(consumeNotifcation:)
                                                                              name:MyNotification
                                                                             object:nil];
}

- (void)viewDidUnload {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                                                    name:MyNotification
                                                                                   object:nil];
    [super viewDidUnload];
}

- (void)consumeNotification:(NSNotification * notification) {
    // Grab the information from the notification.
}

// In view controller Y
NSString * const MyNotification = @"MyNotification";
NSString * const MyObject = @"MyObject";

- (void)viewWillDisappear:(bool)animated {
    [super viewWillDisappear:animated];

    NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                                [self object], MyObject,
                                                nil];

    [[NSNotificationCenter defaultCenter] postNotificationName:MyNotification
                                                                                           object:self
                                                                                       userInfo:userInfo];
}

Pros/Cons

Obviously this is a lot more code than the other options. It comes with two key advantages. First, this pattern makes multiple, unrelated observers very easy. Second, it allows for looser coupling between views. X and Y (and any other views/non-views) essentially don't know anything about each other. They simply send or receive information through NSNotificationCenter. One place I use this pattern is with an app-wide settings view. When the settings are changed, different parts of the app may need to know immediately when the change is made. For example, I may have a setting that can turn off downloading over the cell network. Both the networking code and the UI will need to know about this change. The notification makes sure that they are informed immediately without needing to hard code the interactions.

Conclusion

When passing information to a previous view controller, favor blocks. However, keep the other options in mind. They have their advantages too.

Other Information

For more information on UIStoryboards, check out my best practices.