YGben

Associated Object


关联对象

Objective-C Runtime 一直是OC语言中很重要的特性。当初刚开始学习的时候,就知道 OC = C + Runtime 的说法,但一直没机会用Runtime。

偶然看到官方文档这样一段话:

Objective-C is more than just a language that is compiled down to machine code. Instead, it requires a runtime system in place to execute that code.

可见Runtime的重要。

基本的对象调用,前段时间被禁止的热更新框架 JPATCH 都基于Runtime提供的支持。很多东西要学习总结啊,这篇从Associated Objects说起。以后有关Runtime,遇到一个总结一个。

1. Runtime的资料

  • 阅读官方文档,文中开头:

    The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps.

    Runtime 的存在让OC语言有了”动态特性”的法力。

  • 文档中介绍了库中功能函数。本篇详细看Associative References

func objc_setAssociatedObject(Any!, UnsafeRawPointer!, Any!, objc_AssociationPolicy)

Sets an associated value for a given object using a given key and association policy.

func objc_getAssociatedObject(Any!, UnsafeRawPointer!)

Returns the value associated with a given object for a given key.

func objc_removeAssociatedObjects(Any!)

Removes all associations for a given object.

  • objc/runtime 源码是开源的, 需要点C++才看的懂。

  • 有一篇啸笑天Objective-CRuntime基本介绍了Runtime。没事可以看看。

2. Associated Object

要想 关联对象,#include <objc/runtime.h> 就需要引入runtime。

代码测试

An associative reference links one object with another, in a similar way to a property or instance variable

简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void *overviewKey0 = "overviewKey0";
// static void 声明全局静态变量 用作 key
static void *overviewKey1 = "overviewKey1";
NSArray *array = [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];
NSString * overview0 = [[NSString alloc] initWithFormat:@"Four"];
NSString * overview1 = [[NSString alloc] initWithFormat:@"Three"];
// 通过key值 和 关联策略 把overview 和array联系起来。
objc_setAssociatedObject(array, overviewKey0, overview0, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(array, overviewKey1, overview1, OBJC_ASSOCIATION_RETAIN);
// 通过key值获取array联系的对象。
NSString *associatedObject0 = (NSString *)objc_getAssociatedObject(array, overviewKey0);
NSString *associatedObject1 = (NSString *)objc_getAssociatedObject(array, overviewKey1);
NSLog(@"associatedObject0 : %@ \n", associatedObject0);
NSLog(@"associatedObject1 : %@ \n", associatedObject1);
// 解除联系
objc_removeAssociatedObjects(array);
associatedObject0 = (NSString *)objc_getAssociatedObject(array, overviewKey0);
associatedObject1 = (NSString *)objc_getAssociatedObject(array, overviewKey1);
NSLog(@"associatedObject0 : %@ \n", associatedObject0);
NSLog(@"associatedObject1 : %@ \n", associatedObject1);

输出结果:

1
2
3
4
associatedObject0 : Four
associatedObject1 : Three
associatedObject0 : (null)
associatedObject1 : (null)

  • 从例子看出objc_removeAssociatedObjects会接触所有对象的关联。不采用。使用 objc_setAssociatedObject(array, overviewKey1, nil, OBJC_ASSOCIATION_RETAIN); 就像API里讲的:

    Typically you should use \c objc_setAssociatedObject
    with a nil value to clear an association.

  • key 的数据类型是 (void *) 静态的函数指针。通常使用静态全局变量做键。

经典应用

  • AFNetworking/UIKit+AFNetworking/UIImageView+AFNetworking
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static char kAFImageRequestOperationKey;
    @property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFHTTPRequestOperation *af_imageRequestOperation;
    - (AFHTTPRequestOperation *)af_imageRequestOperation {
    return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, &kAFImageRequestOperationKey);
    }
    - (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation {
    objc_setAssociatedObject(self, &kAFImageRequestOperationKey, imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

AFNetworking在 UIImageView 的category上用了关联对象来保持一个operation对象,用于从网络上某URL异步地获取一张图片。

  • facebook/KVOController
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static void *NSObjectKVOControllerKey = &NSObjectKVOControllerKey;
    @property (nonatomic, strong) FBKVOController *KVOController;
    - (FBKVOController *)KVOController
    {
    id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
    // lazily create the KVOController
    if (nil == controller) {
    controller = [FBKVOController controllerWithObserver:self];
    self.KVOController = controller;
    }
    return controller;
    }
    - (void)setKVOController:(FBKVOController *)KVOController
    {
    objc_setAssociatedObject(self, NSObjectKVOControllerKey, KVOController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

KVOController 为 NSObject 添加了一个包含 KVOController 方法的 category,关联了KVOController这个“观察对象”。

###3. 感谢
《Effective Objective-C 2.0》第10条;
Mattt Thompson撰写、 Croath Liu翻译 Associated Objects