OC Runtime之Associated Objects

什么是Associated Objects?

有时,我们希望为某个对象关联一些信息。一般地,我们会子类化,但是“黑魔法”Associated Objects(关联对象)使用起来就更方便了。使用关联对象,我们可以不用修改类的定义而为其对象增加存储空间(在 Objective-C 中可以通过 Category 给一个现有的类添加属性,但是却不能添加实例变量,这似乎成为了 Objective-C 的一个明显短板。然而值得庆幸的是,我们可以通过 Associated Objects 来弥补这一不足)。与它相关在中有3个C函数,它们可以让对象在运行时关联任何值:

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

    根据给定的key和关联策略给对象设置关联值

  • id objc_getAssociatedObject(id object, void *key)

    根据给定的key和关联策略获取对象关联值

  • void objc_removeAssociatedObjects(id object)

    移除所有关联值

下面将展示如何用这些函数为UIAlertView添加点击回调:

//UIAlertView+AssociatedObjects.h
#import <UIKit/UIKit.h>
@interface UIAlertView (AssociatedObjects)
@property (strong, nonatomic) id associatedObject;//添加的关联对象
@end

//UIAlertView+AssociatedObjects.m
#import "UIAlertView+AssociatedObjects.h"
#import <objc/runtime.h>

@implementation UIAlertView (AssociatedObjects)

- (void)setAssociatedObject:(id)associatedObject {
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN);
}

- (id)associatedObject {
    return objc_getAssociatedObject(self, _cmd);
}
@end


//ViewController.h
#import "ViewController.h"
#import "UIAlertView+AssociatedObjects.h"
typedef void (^AssociatedBock)(NSInteger);

- (IBAction)showAlertView {
    UIAlertView *alert = [[UIAlertView alloc]
                          initWithTitle:@"Question"
                          message:@"What do you want to do?"
                          delegate:self
                          cancelButtonTitle:@"Cancel"
                          otherButtonTitles:@"Continue", nil];
    __weak typeof(self) weakSelf = self;
    AssociatedBock block = ^(NSInteger buttonIndex){
        if (0 == buttonIndex) {
            [weakSelf doCancel];
        } else {
            [weakSelf doContinue];
        }
    };
    alert.associatedObject = block;
    [alert show];
}
- (void)doCancel {
    NSLog(@"cancel..............");
}

- (void)doContinue {
    NSLog(@"continue...............");
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    AssociatedBock block = (AssociatedBock)alertView.associatedObject;
    block(buttonIndex);
}

关联对象的特性

被关联到对象的值根据使用的objc_AssociationPolicy类型不同表现出不同的特性:

img

通过OBJC_ASSOCIATION_ASSIGN分配的弱关联对象并不是完全和weak修饰符引用一样(对象初始化与释放时被置空),反而更像是unsafe_unretained,所以你需要在访问弱关联对象时稍微注意一下。

根据WWDC2011,Session322对对象释放时间的描述,associated objects清除在对象生命周期中很晚才执行,通过被NSObject -dealloc方法调用的object_dispose()函数完成。

移除关联对象

一种方法是试图在某个时刻调用objc_removeAssociatedObjects()函数来移除关联对象,然而,根据苹果文档描述,你不大可能有需求要自己去调用:

这个函数的主要目的是很容易的让对象恢复成它“原始状态”,你不应该使用它来移除关联的对象,因为它也会移除掉包括其他地方加入的全部的关联对象。所以一般你只需要通过调用objc_setAssociatedObject并传入nil值来清除关联值.

作用

1.添加私有变量来帮助实现细节 。当拓展一个内置类时,可能有必要跟踪一些额外的状态,这是关联对象最普遍的应用场景。例如:AFNetworking中在UIImageView的分类中使用关联对象来存储一个请求操作对象(operation object),用于异步的从远程获取图片。

2.添加公共属性来设置分类的特性 。有时候,通过添加一个属性让一个分类更加灵活,而不是作为函数参数。这种情况下,使用关联对象作为一个公开的属性是可接受的解决方案。还是拿前面AFNetworking的例子来说,UIImageView的分类中imageResponseSerializer属性允许图片视图随意的使用一个过滤器,或者在图片请求并缓存之前就可以修改它的渲染。

3.为KVO创建一个关联的观察者(observer)。当在一个分类中使用KVO的时候,推荐使用一个自定义的关联对象作为观察者,而不是对象自己观察自己。

ps:关联对象应该被当做最后的手段来使用,不要滥用。比如:在不必要的时候使用关联对象,使用关联对象来保存一个可以被推算出来的值等。

参考:

Effective Objective-C 2.0

来自NSHipster的原文:http://nshipster.com/associated-objects/

中文翻译http://www.cocoachina.com/industry/20140219/7847.html

大神博客:http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/#jtss-tsina