Effective Objective-C 2.0 — 第10条:在既有类中使用关联对象存放自定义数据

  • 可以通过“关联对象”机制来把两个对象连起来
  • 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”
  • 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的 bug

目的:需要在对象中存放相关信息,方法一,从对象所属的类中继承一个子类,然后改用这个子类对象。

  方法二:关联对象(Associated Object),对象通过“键”来区分。

可以把某对象想象成NSDictionary,把关联到对象上调用[object setObject:value forKey:key] 与 [object objectForKey:key]方法。但是设置关联对象那个时用的键(key)是个“不透明的指针“(opaque pointer),

如果在两个键上调用”isEqual“方法返回值是 YES, 那么NSDictionary就认为两者相等;然而设置关联对象时,二者必须是完全相同的指针才行。鉴于此,在设置关联对象值时,通常使用静态全局变量做键。

下列方法管理关联对象:

  void objc_setAssociatedObject (id object, void*key, id value, objc_AssociationPolicy policy)

  此方法以给定的键和策略为某对象设置关联对象值

  id objc_getAssociatedObject(id object, void*key)

  此方法根据给定的键从某对象中获取相应的关联对象值

  void objc_removeAssociatedObjects(id object)

  此方法移除指定对象的全部关联对象

例子:UIAlertView 类,警告信息,当用户按下按钮关闭该视图时,需要用委托协议(delegate protocol)在处理此动作,但是要想设置好这个委托机制,就得把创建警告视图和处理按钮动作的代码分开。比兔使用UIAlertView 时,一般都会这么写:

- (void)askUserAQuestion {
    UIAlertView *alert = [[UIAlertView alloc]  initWithTitle:@"Question"
                                                             message:@"What do you want to do?"
                                                             delegate:self
                                                             cancelButtonTitle:@"Cancel"
                                                             otherButtonTiles:@"Continue",nil];
            [alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView *)alertView
            clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttoIndex == 0) {
           [self doCancel];
    } else {
            [self doContinue];
    }
}

如果想在同一个类里处理多个警告信息视图,那么代码就会变得更为复杂,必须在delegate方法中检查传入的 alertView 参数

可以使用关联对象 在创建警告视图的时候直接把处理每个按钮的逻辑都写好:创建完警告视图之后,设定一个与之关联的“块” block,等到执行 delegate 方法是再将其读出来,代码如下

#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";  
- (void)askUserAQuestion { 
       UIAlertView *alert = [[UIAlertViewalloc] initWithTitle:@"Question"
                                        message:@"What do you want to do?"
                                        delegate:self
                                        cancelButtonTitle:@"Cancel"
                                        otherButtonTitles:@"Continue", nil];
    void (^block) (NSInteger) = ^(NSInteger buttonIndex) {
            if (buttonIndex == 0)  {
                [self doCancel]; 
                }  else  {
                [self doContinue];
                }
    };
       objc_setAssociateObject(alert, EOCMyAlertViewKey, block, BJC_ASSOCIATION_COPY);
        [alert show];
}
// UIAlertViewDelegate protocol method 关联类型等效的@property 属性为
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    void (^block) (NSInteger) = objc_getAssociateObject(alertView, EOCMyAlertViewKey);
    block(buttonIndex);
}                                       

注意:块可能要捕获 (capture)某些变量,这也许会造成“保留环)(retain cycle)