前面将类的加载说完了,下面说几个问题,
1.方法排序究竟是怎样排序的。2.我们知道dyld将类会编译成Macho文件,我们在探究类的加载的时候,会看到cls->data()方法,这个得到的就是class_ro_t类型的方法列表,那什么时候它会编译成class_ro_t类型的呢(也就是格式怎么来的)3.类扩展分析。4.关联对象在底层死怎么实现的?下面我们带着这些问题进行探索
方法排序
我们前面讲到fixupMethodList是排序方法,我们看下方法实现
我们是根据1215行的sort来进行判断的,SortBySELAddress这个方法是用方法地址进行排序的。我看下在排序前的方法列表打印
发现此时是无序的,下面是排序后的方法打印
我们看到方法名的地址是从小到大进行排列的。这里不是通过imp排列的。
我们看下SortBySELAddress方法
我们发现SortBySELAddress是跟name相关联的,在fixupMethodList方法里,只有sel_registerNameNoLock是跟name相关联的。下面我们看下sel_registerNameNoLock方法实现
红框内的方法基本不走,除非进行sel的alloc开辟空间。
我们看下search_builtins方法的具体实现
关键信息就是_dyld_get_objc_selector,这个方法就是在dyld源码里了,我们去dyld源码看看
如果当前的类存在就去共享缓存去拿,如果是dyld3就在执行一次。这是因为系统的方法会在共享缓存内,我们自己写的方法是不在共享缓存内的,所以才会有如果存在就去共享缓存中去拿。所以我们自己写的方法就必须走dyld3。
我们继续向下研究
1362-1364行就是判断是否为空地址,如果为空就返回。如果不是就拿到imageAndOffset,而target是从selName拿到的,然后赋值给imageAndOffset.raw。1731行就是当前段的首地址进行偏移。所以这个值是不断的变化的。
通过上面可以知道方法排序显示同名方法排序,而后是按照方法名的地址大小进行排序的。
class_ro_t格式怎么来的
先看下class_ro_t的结构
700-715行都是属性变量,在我们研究过程成基本上是不看的,我们知道class_ro_t存在Macho中,在编译期就处理完毕了,这意味着有个方法在编译期就能读到class_ro_t。那么这个时候就需要去看LLVM源码
这就是LLVM源码中的class_ro_t结构,我们发现read方法,下面看下read方法实现。
131-142行就是确定size大小,大小包含下面这些,后面都有注释解释。147行就是从内存读取process,读取完成后,来到157-170行就是对class_ro_t的属性进行赋值。那什么时候调用的呢?我们继续向上看还会发现class_rw_t,class_rw_ext_t其实这些都相当于模板。
我们继续向下找的时候找到Read_class_row方法,发现这个方法里存在class_ro->Read下图红框所示
那么什么时候调用Read_class_row?
我们看到上面的两个方法红框名字相同,上面是调用地方,最后一个方法就是如果初始化ClassDescriptorV2,就会调用Read_class_row也就是完成class_ro_t。
类的扩展
上面的文章OC底层原理之-类的加载过程-下( 类及分类加载)主要讲了分类的加载,提到分类我们就会想到类的扩展
补充分类和类扩展的区别
1.category:类别,分类
- 专门给类添加新的方法
- 不能给类加成员属性、添加成员变量,也无法取到
- 注意:可以通过runtime给分类添加属性
- 分类中用@property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法的实现和带下划线的成员变量。`(在分类用@property声明属性,用本类能够赋值,就是因为生成了getter,setter声明,但是由于没有实现,所以会崩)
2.extension:类扩展
- 可以说成是特殊的分类,也称作匿名分类
- 可以给类添加成员属性,但是是私有变量
- 可以给类添加方法,也是私有方法
类的扩展底层实现
我们准备代码
@interface Man : NSObject
- (void)instanceMethod;
- (void)classMethod;
@end
@interface Man ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
@implementation Man
- (void)ext_instanceMethod{
}
- (void)ext_classMethod{
}
- (void)instanceMethod{
}
- (void)classMethod{
}
@end
类的扩展一定是在声明之后,实现之前。其实我们是经常用的,类的.h就是声明,.m就是实现和扩展。
我们将main.m文件转成.cpp文件,看下C++的实现。我们先看属性ext_name的实现,带下划线的ext_name,并放到Man_IMP中
我们再看下有没有setter和getter方法
发现是存在的,下面我们再看下方法,以ext_instanceMethod来看
我们看到这个想起来cache_t,在编译器就编译成这样的,我们看到instanceMethod声明的方法。
只有实现了才会在method_list_t中。
下面我们重新准备下代码
// Person.m中
@implementation Person
+ (void)load{
}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
}
- (void)ext_classMethod{
}
@end
// Person+PEXT.h中
@interface Person ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
我们在readClass中打断点,运行,读取当前Person的方法列表
我们看到类扩展方法已经加载到方法列表中去了。
类的扩展和分类不同,类的扩展不会通过attachCategories进行加载,是直接加载到类方法列表里。
关联对象
准备代码
@implementation Person (A)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)setCate_name:(NSString *)cate_name{
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
person.cate_name = @"6666";
[person ext_instanceMethod];
NSLog(@"%p",person);
}
return 0;
}
我们在分类里添加属性,在main函数里进行赋值,如果不进行关联对象,就会报错,我们进行关联对象,赋值后肯定会进入objc_setAssociatedObject方法。我们看下objc_setAssociatedObject方法,第一个参数为对象,第二个参数是标识符,第三个参数是Value值,第四个参数是策略。
我们来看下objc_setAssociatedObject实现
这个相当于接口模式,暴露给外面的永远不变,内部进行处理,我们看下SetAssocHook方法实现
当我们走到SetAssocHook方法的时候,必然会走到_base_objc_setAssociatedObject方法,我们打断点
发现确实走到这个方法里了,下面我们再看下_object_set_associative_reference方法实现
我们看169行就是包装对象,DisguisedPtr是个类型,ObjcAssociation也是对policy - value包装。
我们看下acquireValue方法
如果是retain类型就调用objc_retain,如果是copy类型就发送copy消息。也就是对我们的策略类型进行处理,下面177行AssociationsManager方法,我们看下实现
其中112-113行是个析构函数这个函数参数是锁,之所以加锁是因为防止多线程重复创建,但不是不能创建。下面我们我们模拟下这个析构函数
运行代码
发现相当于是作用域。
回到_object_set_associative_reference方法,我们可以发现AssociationsManager可以有多个。下面就到了AssociationsHashMap,这个是HashMap这是个哈希表,这个是唯一的。因为AssociationsManager方法里看到AssociationsHashMap是通过_mapStorage.get()方法获得,而_mapStorage是通过static声明的,是静态变量,也就是AssociationsHashMap是通过静态变量_mapStorage获取的,所以是全场唯一的。
下面我们运行看下数据结构
我们看到association打印的value就是我们的赋值,继续运行再打印
我们看到获取的是空,是因为还没有查找到相应的递归查找位域
继续往下走,来到183行,我们看下refs_result类型
我们看到类型很长,我们该怎么处理呢?我们将它复制出来,
我们通过<和>对应关系,将这个类型分开,其中122-130行是一个参数,第二个参数是个bool类型。我们回到_object_set_associative_reference方法
判断refs_result的第二个参数,就是bool类型,判断就是如果是第一次进来就会走185行方法。我们不管refs_result第一个一大堆的东西,我们只管bool值,下面我们看下try_emplace方法实现
我们只需要看下make_pair,268-275行:拿到给到的TheBucket(值)和key(关联对象)去查找,如果找到了就直接返回,如果找不到就将TheBucket和key插入进去,并返回。
我们回到_object_set_associative_reference方法,看下setHasAssociatedObjects实现
设置标记为,对isa的has_assoc设置为true。我们看下如果value为空值的操作
做的操作就是删除。也就是当我们赋值为空的时候,它将其删掉,移除。
下面我们再来看看try_emplace查找TheBucket怎么找的,我们看下LookupBucketFor实现
我们发现两个方法一样,下面的方法是个重载函数,701行调用的上面的方法。为什么呢?
我们看下两个方法的参数,发现第一个方法FoundBucket带有const,而另一个没有,我们再看看下701行调用参数是ConstFoundBucket,而ConstFoundBucket在699行用const修饰了,外界调用的是下面的方法,我们上面也看到TheBucket是没有用const修饰的。第二个方法700行是查找TheBucket,如果查找到了,就给FoundBucket,回调出去。我们看下上面的方法实现
总览整个方法就是TheBucket查找过程,这个过程和cache_t的方法查找相似,都是循环查找,找不到平移知道找到或者查完为止。
回到try_emplace方法,继续往下走找到TheBucket,我们打印下TheBucket
下面的类型是不是有些类似,这个其实跟refs_result的最后一个长的相同。说明将TheBucket藏在这里。
继续到try_emplace方法,如果找到这个TheBucket就会将TheBucket通过make_pair插入值。然后返回true。到此结束。
画了张流程图关联对象的
总结
上面讲了文章开头说的几个问题。重点是关联对象,这里我们在对关联对象进行总结:
关联对象:设值流程
- 1.创建一个 AssociationsManager 管理类
- 2.获取唯一的全局静态哈希Map
- 3.判断是否插入的关联值是否存在:
- 存在走第4步
- 不存在就走 : 关联对象插入空流程
- 4.创建一个空的 ObjectAssociationMap 去取查询的键值对
- 5.如果发现没有这个 key 就插入一个 空的 BucketT进去 返回
- 6.标记对象存在关联对象
- 7.用当前 修饰策略 和 值 组成了一个 ObjcAssociation 替换原来 BucketT 中的空
- 8.标记一下 ObjectAssociationMap 的第一次为 false
关联对象插入空流程
- 1.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
- 2.清理迭代器
- 3.其实如果插入空置 相当于清除
关联对象:取值流程
- 1.创建一个 AssociationsManager 管理类
- 2.获取唯一的全局静态哈希Map
- 3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
- 4.如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value)
- 5.找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
- 6.返回_value