我们发现ObjcAssociation存储着_policy、_value,而这两个值我们可以发现正是我们调用objc_setAssociatedObject函数传入的值,也就是说我们在调用objc_setAssociatedObject函数中传入的value和policy这两个值最终是存储在ObjcAssociation中的。
现在我们已经对AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四个对象之间的关系有了简单的认识,那么接下来我们来细读源码,看一下objc_setAssociatedObject函数中传入的四个参数分别放在哪个对象中充当什么作用。
重新回到_object_set_associative_reference函数实现中
细读上述源码我们可以发现,首先根据我们传入的value经过acquireValue函数处理获取new_value。acquireValue函数内部其实是通过对策略的判断返回不同的值
之后创建 AssociationsManager manager 以及拿到manager内部的AssociationsHashMap即associations。 之后我们看到了我们传入的第一个参数object,object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object。
DISGUISE函数其实仅仅对object做了位运算
之后我们看到被处理成new_value的value,同policy被存入了ObjcAssociation中。
而ObjcAssociation对应我们传入的key被存入了ObjectAssociationMap中。
disguised_object和ObjectAssociationMap则以key-value的形式对应存储在associations中也就是AssociationsHashMap中。
如果我们value设置为nil的话那么会执行下面的代码
从上述代码中可以看出,如果我们设置value为nil时,就会将关联对象从ObjectAssociationMap中移除。
最后我们通过一张图可以很清晰的理清楚其中的关系
通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。
由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。
objc_getAssociatedObject函数
objc_getAssociatedObject内部调用的是_object_get_associative_reference
_object_get_associative_reference函数
从_object_get_associative_reference函数内部可以看出,向set方法中那样,反向将value一层一层取出最后return出去。
objc_removeAssociatedObjects函数
objc_removeAssociatedObjects用来删除所有的关联对象,objc_removeAssociatedObjects函数内部调用的是_object_remove_assocations函数
_object_remove_assocations函数
上述源码可以看出_object_remove_assocations函数将object对象向对应的所有关联对象全部删除。
关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中,如果设置关联对象为nil,就相当于是移除关联对象。
此时我们我们在回过头来看objc_AssociationPolicy policy 参数: 属性以什么形式保存的策略。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性 OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性 OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性 };
我们会发现其中只有RETAIN和COPY而为什么没有weak呢? 总过上面对源码的分析我们知道,object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object。
disguised_ptr_t disguised_object = DISGUISE(object);
而同时我们知道,weak修饰的属性,当没有拥有对象之后就会被销毁,并且指针置位nil,那么在对象销毁之后,虽然在map中既然存在值object对应的AssociationsHashMap,但是因为object地址已经被置位nil,会造成坏地址访问而无法根据object对象的地址转化为disguised_object了。
相关问题
问:Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
答:Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法
问:load、initialize的区别,以及它们在category重写的时候的调用的次序。
答:区别在于调用方式和调用时刻
- 调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
- 调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
- 调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。
以上文章整理自:https://juejin.cn/post/6844903602524274696、https://juejin.cn/post/6844903605347057672