iOS底层原理总结 - 关联对象实现原理

面试题

问: Category可否添加成员变量?若是能够,如何给Category添加成员变量?面试

答:不能直接添加成员变量,可是能够经过runtime的方式间接实现添加成员变量的效果。bash

RunTime为Category动态关联对象

使用RunTime给系统的类添加属性,首先须要了解对象与属性的关系。咱们经过以前的学习知道,对象一开始初始化的时候其属性为nil,给属性赋值其实就是让属性指向一块存储内容的内存,使这个对象的属性跟这块内存产生一种关联。markdown

那么若是想动态的添加属性,其实就是动态的产生某种关联就行了。而想要给系统的类添加属性,只能经过分类。函数

这里给NSObject添加name属性,建立NSObject的分类 咱们可使用@property给分类添加属性post

@property(nonatomic,strong)NSString *name;
复制代码

经过探寻Category的本质咱们知道,虽然在分类中能够写@property 添加属性,可是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,须要咱们本身去实现。学习

方法一:咱们能够经过使用静态全局变量给分类添加属性
static NSString *_name;
-(void)setName:(NSString *)name
{
    _name = name;
}
-(NSString *)name
{
    return _name;
}
复制代码

可是这样_name静态全局变量与类并无关联,不管对象建立与销毁,只要程序在运行_name变量就存在,并非真正意义上的属性。ui

方法二:使用RunTime动态添加属性

RunTime提供了动态添加属性和得到属性的方法。atom

-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @"name");    
}
复制代码
  1. 动态添加属性
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
复制代码

参数一:id object : 给哪一个对象添加属性,这里要给本身添加属性,用self。 参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在**objc_getAssociatedObject中经过次key得到属性的值并返回。 参数三:id value** : 关联的值,也就是set方法传入的值给属性去保存。 参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。 有如下几种spa

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     // 指定相关的对象被复制,原子性   
};
复制代码

key值只要是一个指针便可,咱们能够传入@selector(name)指针

  1. 得到属性
objc_getAssociatedObject(id object, const void *key);
复制代码

参数一:id object : 获取哪一个对象里面的关联的属性。 参数二:void * == id key : 什么属性,与**objc_setAssociatedObject**中的key相对应,即经过key值取出value。

  1. 移除全部关联对象
- (void)removeAssociatedObjects
{
    // 移除全部关联对象
    objc_removeAssociatedObjects(self);
}

复制代码

此时已经成功给NSObject添加name属性,而且NSObject对象能够经过点语法为属性赋值。

NSObject *objc = [[NSObject alloc]init];
objc.name = @"xx_cc";
NSLog(@"%@",objc.name);
复制代码

能够看出关联对象的使用很是简单,接下来咱们来探寻关联对象的底层原理

关联对象实现原理

实现关联对象技术的核心对象有

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation
    其中Map同咱们平时使用的字典相似。经过key-value一一对应存值。

对关联对象技术的核心对象有了一个大概的意识,咱们经过源码来探寻这些对象的存在形式以及其做用

objc_setAssociatedObject函数

来到runtime源码,首先找到objc_setAssociatedObject函数,看一下其实现

objc_setAssociatedObject函数实现

咱们看到其实内部调用的是_object_set_associative_reference函数,咱们来到_object_set_associative_reference函数中

_object_set_associative_reference函数

_object_set_associative_reference函数内部
_object_set_associative_reference函数内部咱们能够所有找到咱们上面说过的实现关联对象技术的核心对象。接下来咱们来一个一个看其内部实现原理探寻他们之间的关系。

AssociationsManager

经过AssociationsManager内部源码发现,AssociationsManager内部有一个AssociationsHashMap对象。

AssociationsManager内部

AssociationsHashMap

咱们来看一下AssociationsHashMap内部的源码。

AssociationsHashMap内部

经过AssociationsHashMap内部源码咱们发现AssociationsHashMap继承自unordered_map首先来看一下unordered_map内的源码

unordered_map内部分源码

从unordered_map源码中咱们能够看出_Key_Tp也就是前两个参数对应着map中的Key和Value,那么对照上面AssociationsHashMap内源码发现_Key中传入的是disguised_ptr_t_Tp中传入的值则为ObjectAssociationMap*

紧接着咱们来到ObjectAssociationMap中,上图中ObjectAssociationMap已经标记出,咱们发现ObjectAssociationMap中一样以key、Value的方式存储着ObjcAssociation

接着咱们来到ObjcAssociation中

ObjcAssociation

咱们发现ObjcAssociation存储着_policy_value,而这两个值咱们能够发现正是咱们调用objc_setAssociatedObject函数传入的值,也就是说咱们在调用objc_setAssociatedObject函数中传入的value和policy这两个值最终是存储在ObjcAssociation中的。

如今咱们已经对AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四个对象之间的关系有了简单的认识,那么接下来咱们来细读源码,看一下objc_setAssociatedObject函数中传入的四个参数分别放在哪一个对象中充当什么做用。

从新回到_object_set_associative_reference函数实现中

_object_set_associative_reference函数内部

细读上述源码咱们能够发现,首先根据咱们传入的value通过acquireValue函数处理获取new_value。acquireValue函数内部实际上是经过对策略的判断返回不一样的值

acquireValue函数内部

以后建立AssociationsManager manager;以及拿到manager内部的AssociationsHashMap即associations。 以后咱们看到了咱们传入的第一个参数object object通过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object

DISGUISE函数

DISGUISE函数其实仅仅对object作了位运算

以后咱们看到被处理成new_value的value,同policy被存入了ObjcAssociation中。 而ObjcAssociation对应咱们传入的key被存入了ObjectAssociationMap中。 disguised_object和ObjectAssociationMap则以key-value的形式对应存储在associations中也就是AssociationsHashMap中。

关键代码

若是咱们value设置为nil的话那么会执行下面的代码

value为nil

从上述代码中能够看出,若是咱们设置value为nil时,就会将关联对象从ObjectAssociationMap中移除。

最后咱们经过一张图能够很清晰的理清楚其中的关系

关联对象底层对象关系

经过上图咱们能够总结为:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。

由此咱们能够知道关联对象并非放在了原来的对象里面,而是本身维护了一个全局的map用来存放每个对象及其对应关联属性表格。

objc_getAssociatedObject函数

objc_getAssociatedObject内部调用的是_object_get_associative_reference

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函数

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了。


本文是对底层原理学习的总结,若是有不对的地方请指正,欢迎你们一块儿交流学习 xx_cc 。

相关文章
相关标签/搜索