动态语言的最大好处,就是灵活性,对于Objective-C来说,能在运行时动态地为类增加方法和实例变量是很多其它语言羡慕不已的能力。现在说说为类增加实例变量用到的技术:联合存储。
一、联合存储的实现方式
下面这段代码实现了为Duck类增加color属性:
Duck+associative.h文件
- #import "Duck.h"
-
- @interface Duck (associative)
-
- @property (nonatomic, retain) NSString *color;
-
- @end
Duck+associative.m文件
- #import "Duck+associative.h"
- #import <objc/runtime.h>
-
- @implementation Duck (associative)
-
- static char colorKey = NULL;
-
- - (NSString *)color {
- return objc_getAssociatedObject(self, &colorKey);
- }
-
- - (void)setColor:(NSString *)aColor {
- objc_setAssociatedObject(self, &colorKey,
- aColor,
- OBJC_ASSOCIATION_RETAIN);
- }
调用举例:
- Duck *smallDuck = [[Duck alloc] init];
- smallDuck.color = @"red color";
- NSLog(@"duck color:%@",smallDuck.color);
- [smallDuck release];
输出结果:
- 2013-07-18 19:09:26.578 ObjcRunTime[429:403] duck color:red color
至此,我们已经成功的为Duck类增加了一个color属性。
二、为类动态增加属性用到的技术
主要用到了三种设计模式:
1、访问器(accessor)
访问器模式是Cocoa设计模式中很重要的一个,使用它的目的是通过少量的方法(通常时get和set方法)来访问类中每个实例的引用。通过该技术,尽管Duck类没有color实例变量,但是通过联合存储,依然可以实现同访问实例变量完全一样的效果。这些对于类的使用者来说,屏蔽了实现细节。
可以说,访问器模式是通过联合存储实现为类增加属性的必要前提。
2、类别(category)
类别可以在运行时为类动态的增加方法,这是可以利用访问器模式实现为类增加属性的基础。
3、联合存储(associative storage)
通过类别和访问器,再结合联合存储技术,我们完成了为类增加属性的功能。这一切让用户觉得好像真的增加新的实例变量了,但是
实际上我们只是通过访问器模拟出来了一个,而不是真正的增加了。
三、联合存储的实现原理
从上面的例子可以看出,为类增加属性看起来是so easy的事情,主要是调了objc_setAssociatedObject和objc_getAssociatedObject两个方法。我的疑问是为类增加的属性对应的对象值存储在哪了呢?下面通过这两个方法的实现部分来寻找答案:
1、objc_setAssociatedObject方法的实现部分:
- void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
- if (UseGC) {
-
- if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
- value = objc_msgSend(value, @selector(copy));
- }
- auto_zone_set_associative_ref(gc_zone, object, key, value);
- } else {
-
-
- _object_set_associative_reference(object, key, value, policy);
- }
- }
从上述方法中可以看出,objc_setAssociatedObject实际上调用的是:
2、_object_set_associative_reference方法的实现部分:
- __private_extern__ void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
-
- uintptr_t old_policy = 0;
-
- id new_value = value ? acquireValue(value, policy) : nil, old_value = nil;
- {
- AssociationsManager manager;
- AssociationsHashMap &associations(manager.associations());
- if (new_value) {
-
-
- AssociationsHashMap::iterator i = associations.find(object);
- if (i != associations.end()) {
-
-
-
- ObjectAssociationMap *refs = i->second;
- ObjectAssociationMap::iterator j = refs->find(key);
- if (j != refs->end()) {
-
- ObjcAssociation &old_entry = j->second;
- old_policy = old_entry.policy;
- old_value = old_entry.value;
- old_entry.policy = policy;
- old_entry.value = new_value;
- } else {
-
- (*refs)[key] = ObjcAssociation(policy, new_value);
- }
- } else {
-
-
-
- ObjectAssociationMap *refs = new ObjectAssociationMap;
- associations[object] = refs;
- (*refs)[key] = ObjcAssociation(policy, new_value);
- _class_assertInstancesHaveAssociatedObjects(object->isa);
- }
- } else {
-
-
- AssociationsHashMap::iterator i = associations.find(object);
- if (i != associations.end()) {
- ObjectAssociationMap *refs = i->second;
- ObjectAssociationMap::iterator j = refs->find(key);
- if (j != refs->end()) {
- ObjcAssociation &old_entry = j->second;
- old_policy = old_entry.policy;
- old_value = (id) old_entry.value;
- refs->erase(j);
- }
- }
- }
- }
-
- if (old_value) releaseValue(old_value, old_policy);
- }
3、通过这个方法的实现部分可以清楚的看出,在runtime系统中:
①
有一个单例的AssociationsHashMap实例
该实例的生成方式如下:
- AssociationsHashMap &associations() {
- if (_map == NULL)
- _map = new(::_malloc_internal(sizeof(AssociationsHashMap))) AssociationsHashMap();
- return *_map;
- }
②
AssociationsHashMap实例用于保存一个个的ObjectAssociationMap对象
③
每个类都拥有一个ObjectAssociationMap实例,每个类通过联合存储模式保存的键值对也都保存在ObjectAssociationMap实例中。
④Key对应的值无所谓,我们需要的是key的地址,因此定义key时通常的写法是:
- static char colorKey = NULL;
也就是说,说有的数据其实还是保存在AssociationsHashMap实例中,现在似乎一切都豁然开朗了!
四、联合存储的优缺点
1、优点
联合存储的最大的优点,在于它能通过灵活的方式实现为类增加属性。
2、缺点
效率低,使用单条机器指令就可以访问真正的实例变量,但是访问存储在映射表中的值需要多个函数调用,效率问题还是需要注意的。
事实上,目前许多Cocoa类,像NSAttributedString、NSFileManager、NSNotification、NSProcessInfo等都广泛地使用了联合存储。