开发 iOS 时经常用到 UIAlertView 类,该类提供了一种标准视图,可向用户展示警告信息。当用户按下按钮关闭该视图时,需要用委托协议(delegate protocol)来处理此动作,但 是,要想设置好这个委托机制,就得把创建警告视图和处理按钮动作的代码分开。由于代码 分作两块,所以读起来有点乱。比方说,我们在使用 UIAlertView 时,一般都会这么写:
- (void)askUserAQuestion { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil]; [alert show]; }
// UIAlertViewDelegate protocol method - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { [self doCancel]; } else { [self doContinue]; } }
如果想在同一个类里处理多个警告信息视图,那么代码就会变得更为复杂,我们必须在 delegate 方法中检查传入的 alertView 参数,并据此选用相应的逻辑。要是能在创建警告视图 的时候直接把处理每个按钮的逻辑都写好,那就简单多了。这可以通过关联对象来做。创建 完警告视图之后,设定一个与之关联的“块”(block) , 等到执行 delegate 方法时再将其读出 来。此方案的实现代码如下:
-(void)askUserAQuestion{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"第一个Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil]; void(^block)(NSInteger) = ^(NSInteger buttonIndex){ if (buttonIndex == 0) { NSLog(@"00000"); }else{ NSLog(@"111111"); } }; objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY); [alert show]; } -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ void(^block) (NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey); block(buttonIndex); }
以这种方式改写之后,创建警告视图与处理操作结果的代码都放在一起了,这样比原 来更易读懂,因为我们无须在两部分代码之间来回游走,即可明白警告视图的用处。但是, 采用该方案时需注意:块可能要捕获(capture)某些变量,这也许会造成“保留环”(retain cycle)。
正如大家所见,这种做法很有用,但是只应该在其他办法行不通时才去考虑用它。若是 滥用,则很快就会令代码失控,使其难于调试。“保留环”产生的原因很难查明,因为关联对 象之间的关系并没有正式的定义(formal definition),其内存管理语义是在关联的时候才定义 的,而不是在接口中预先定好的。使用这种写法时要小心,不能仅仅因为某处可以用该写法 就一定要用它。想创建这种 UIAlertView 还有个办法,那就是从中继承子类,把块保存为子类 中的属性。若是需要多次用到 alert 视图,那么这种做法比使用关联对象要好。