iOS Recipes - Matt Drance [20]
PRPAlertView/PRPAlertView/PRPAlertView.h
+ (void)showWithTitle:(NSString *)title
message:(NSString *)message
buttonTitle:(NSString *)buttonTitle;
So, how do these blocks help us avoid the delegate? As you saw earlier, PRPAlertView acts as its own delegate and implements a UIAlertViewDelegate method internally to match up the blocks with their respective button titles.
PRPAlertView/PRPAlertView/PRPAlertView.m
- (void)alertView:(UIAlertView *)alertView
willDismissWithButtonIndex:(NSInteger)buttonIndex {
NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
if ([buttonTitle isEqualToString:self.cancelButtonTitle]) {
if (self.cancelBlock) self.cancelBlock();
} else if ([buttonTitle isEqualToString:self.otherButtonTitle]) {
if (self.otherBlock) self.otherBlock();
}
}
On Blocks and Retain Cycles
This recipe uses convenience methods that obscure the created UIAlertView—the initializer is hidden in a private class extension. This reinforces the concept that alerts are “temporary” elements and not really meant to stick around for very long. This concept is more important now that we’re using blocks to handle the buttons, because blocks retain any objects they reference. Imagine your view controller references self from the cancelBlock passed to this class and then saves the alert in a property for future reuse. You’d then have a view controller retaining an alert view...which has a block property...which retains your view controller.
If your view controller is retained by the alert’s block, it won’t be deallocated until the retained alert (and therefore its block) is explicitly released. The alert, though, is stuck in a property on the view controller. This is called a retain cycle, and it can lead to serious memory leaks. We avoid this problem altogether by never exposing the autoreleased alert view we create so that nobody can retain it. Alert views are short-lived and inexpensive to allocate, so there should be no need to hold onto them.
You’ve probably noticed that this class allows only two buttons, which masks the UIAlertView support for a vararg list of “otherButtonTitles.” This keeps the code simpler, and let’s be honest: how many three-or-more-button alert views have you seen out there? If you think you need more than two buttons, you may well have a design problem to work out before you write any more code. That said, it’s not too hard to add vararg support to this class (see Recipe 36, Produce Intelligent Debug Output for an example) and maintain the blocks and titles in a dictionary for easy lookup. We chose to keep it simple—both technically and aesthetically.
With PRPAlertView in place, the controller code becomes much simpler. Here’s what we had to do before PRPAlertView to show a two-button alert with responses for each button:
PRPAlertView/ScrapCode.m
- (void)showAlert {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Uh Oh"
message:@"Something bad happened."
delegate:self
cancelButtonTitle:PRPAlertButtonTitleRunAway
otherButtonTitles:PRPAlertButtonTitleOnward, nil];
[alert show];
[alert release];
}
- (void)alertView:(UIAlertView *)alertView
willDismissWithButtonIndex:(NSInteger)buttonIndex {
NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
if ([buttonTitle isEqualToString:PRPAlertButtonTitleAbort]) {
[self runAway];
} else if ([buttonTitle isEqualToString:PRPAlertButtonTitleOnward]) {
[self proceedOnward];
}
}
Here’s how it looks with PRPAlertView:
PRPAlertView/ScrapCode.m
- (void)showAlert {
[PRPAlertView showWithTitle:@"Uh Oh"
message:@"Something bad happened."
cancelTitle:PRPAlertButtonTitleAbort
cancelBlock:^(void) {
[self runAway];
}
otherTitle:PRPAlertButtonTitleOnward
otherBlock:^(void) {
[self proceedOnward];
}
];
}
With this recipe, we no longer have to worry about memory management, refactoring, coupling, or the complication of multiple