追根问底:Objective-C关联属性原理分析 一.引子 Objective-C是一种动态性很强的语言,所谓动态能力,也可以理解为运行时能力。对于Objective-C开发者来说,动态性所带来的编程便利无处不在。例如通过Category类别来扩展已有类的功能。可以使已有类拥有新的方法和属性。但是,如果你有使用Category来扩展类的属性,你一定了解并非简单的使用@property进行声明即可。例如下面的代码:
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 #import <Foundation/Foundation.h> @interface MyObject : NSObject @end @implementation MyObject @end @interface MyObject (Property )@property (nonatomic , copy ) NSString *addProperty;@end @implementation MyObject (Property )@end int main(int argc, const char * argv[]) { @autoreleasepool { MyObject *object = [[MyObject alloc] init]; object.addProperty = @"HelloWorld" ; NSLog (@"%@" , object.addProperty); } return 0 ; }
代码在编译时不会有任何问题,但是如果运行,就会出现未定义的方法异常。因此如果要扩展类的属性,我们通常会这样实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #import <objc/runtime.h> @implementation MyObject (Property )static NSString *kAddPropertyKey = @"kAddPropertyKey" ;- (void )setAddProperty:(NSString *)addProperty { objc_setAssociatedObject(self , kAddPropertyKey, addProperty, OBJC_ASSOCIATION_COPY); } - (NSString *)addProperty { return objc_getAssociatedObject(self , kAddPropertyKey); } @end
再次运行,就可以正常的对addProperty属性进行存取值了。这里其实就使用到了Objective-C运行时的特性,在Objective-C中,类对象在创建时其所占用的内存空间就已经确定,那么你有没有想过,通过objc_setAssociatedObject这个运行时方法所存储的属性值是如何与当前对象关联起来的,这些数据又是存在哪里的?幸运的时,从objc源码可以清楚的了解关联属性的实现逻辑,这也是我们本篇文章要讨论的重点,了解这里的原理可能不能对你使用关联属性提供多大的帮助,但是这种设计思路定会使你在日常开发中受益匪浅。
二. objc_setAssociatedObject方法的核心原理 通过objc的runtime源码,我们可以看到objc_setAssociatedObject的方法实现如下:
1 2 3 4 5 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); }
这一步无需过多解释,只是调用了一个内部函数,_object_set_associative_reference内部函数是关联属性实现的核心,此函数解析如下:
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { if (!object && !value) return ; if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects" , object, object_getClassName(object)); DisguisedPtr<objc_object> disguised{(objc_object *)object}; ObjcAssociation association{policy, value}; association.acquireValue(); bool isFirstAssociation = false ; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { isFirstAssociation = true ; } auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (!result.second) { association.swap(result.first->second); } } else { auto refs_it = associations.find(disguised); if (refs_it != associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it != refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0 ) { associations.erase(refs_it); } } } } } if (isFirstAssociation) object->setHasAssociatedObjects(); association.releaseHeldValue(); }
可以看到,整个关联属性的过程非常清晰,对于新值是否需要retain以及旧值是否需要release,是由关联策略决定的:
1 2 3 4 5 6 7 8 9 enum { OBJC_ASSOCIATION_SETTER_ASSIGN = 0 , OBJC_ASSOCIATION_SETTER_RETAIN = 1 , OBJC_ASSOCIATION_SETTER_COPY = 3 , OBJC_ASSOCIATION_GETTER_READ = (0 << 8 ), OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8 ), OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8 ), OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, };
acquireValue方法实现如下,其只是判断是否需要retain和copy,之后调用对应的函数:
1 2 3 4 5 6 7 8 9 10 11 12 inline void acquireValue() { if (_value) { switch (_policy & 0xFF ) { case OBJC_ASSOCIATION_SETTER_RETAIN: _value = objc_retain(_value); break ; case OBJC_ASSOCIATION_SETTER_COPY: _value = ((id (*)(id , SEL))objc_msgSend)(_value, @selector (copy )); break ; } } }
在上面第8步中,有调用try_emplace方法来将数据插入到表结构中,此函数插入时会判断要插入的数据是否存在,其返回值会告知调用者是否产生了插入操作,如果已经存在,则此函数会什么都不做。
三. 获取和移除关联属性的原理 现在,我们已经基本清楚了关联属性是如何设置和存储的,再来理解如果获取和移除就非常容易了。
获取关联属性的值是使用objc_getAssociatedObject运行时方法实现的,此方法实现如下:
1 2 3 4 5 id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, key); }
我们还是主要来解析下其调用的_object_get_associative_reference内部方法:
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 id _object_get_associative_reference(id object, const void *key) { ObjcAssociation association{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { ObjectAssociationMap &refs = i->second; ObjectAssociationMap::iterator j = refs.find(key); if (j != refs.end()) { association = j->second; association.retainReturnedValue(); } } } return association.autoreleaseReturnedValue(); }
对于已经关联了属性的对象,我们也可以调用objc_removeAssociatedObjects方法来将关联的所有属性进行移除,此方法实现如下:
1 2 3 4 5 6 7 void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object, false ); } }
_object_remove_assocation内部函数的实现也不复杂,解析如下:
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void _object_remove_assocations(id object, bool deallocating) { ObjectAssociationMap refs{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { refs.swap(i->second); bool didReInsert = false ; if (!deallocating) { for (auto &ref: refs) { if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { i->second.insert(ref); didReInsert = true ; } } } if (!didReInsert) associations.erase(i); } } SmallVector<ObjcAssociation *, 4 > laterRefs; for (auto &i: refs) { if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { if (deallocating) laterRefs.append(&i.second); } else { i.second.releaseHeldValue(); } } for (auto *later: laterRefs) { later->releaseHeldValue(); } }
四. 关联属性如何进行内存管理? 通过前面的介绍,我们知道在关联属性时,可以通过关联策略来设置一些和内存管理相关的选项,在设置关联属性时,如果需要的话,其内部会根据内存管理策略对旧值进行release操作,但是你是否有想过,当对象正常的生命周期结束后,这些关联属性占用的的内存是如何回收的?这就需要我们从系统的dealloc方法中寻找答案了。
系统对象在销毁时,dealloc方法最终会执行到一个名为objc_destructInstance的内部函数,此函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void *objc_destructInstance(id obj) { if (obj) { bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj, true ); obj->clearDeallocating(); } return obj; }
其中会判断要销毁的对象是否有关联属性,如果有,又会调用到_object_remove_assocation函数来进行关联属性的移除,这个函数前面介绍过,内部会处理内存管理问题。
五.关联管理器与表的创建时机 在整个关联属性实现方案中,还有一点我们没有闭环介绍,即全局的关联管理器和Hash表是怎么创建的,何时创建的。我们目前只看到,当要设置或获取关联属性时,直接拿到管理器和Hash表进行使用,并无初始化。其实,这些全局数据结构的创建在runtime初始化时就已经完成,流程路径如下:
1. 调用runtime入口函数_objc_init
2. 通知调用map_images函数
3. 调用map_images_nolock函数
map_images_nolock其中会调用arr_init函数,此函数实现如下:
1 2 3 4 5 6 7 8 void arr_init(void ) { AutoreleasePoolPage::init(); SideTablesMap.init(); _objc_associations_init(); if (DebugScanWeakTables) startWeakTableScan(); }
可以看到,此函数会进行自动释放池,关联属性等逻辑的初始化。
专注技术,热爱生活,交流技术,也做朋友。
—— 珲少 QQ:316045346
同时,如果本篇文章让你觉得有用,欢迎分享给更多朋友,请标明出处。