前面将类的加载说完了,下面说几个问题,
1.方法排序到底是怎样排序的。2.咱们知道dyld将类会编译成Macho文件,咱们在探究类的加载的时候,会看到cls->data()方法,这个获得的就是class_ro_t类型的方法列表,那何时它会编译成class_ro_t类型的呢(也就是格式怎么来的)3.类扩展分析。4.关联对象在底层死怎么实现的?
下面咱们带着这些问题进行探索缓存
咱们前面讲到fixupMethodList是排序方法,咱们看下方法实现 咱们是根据1215行的sort来进行判断的,SortBySELAddress这个方法是用方法地址进行排序的。我看下在排序前的方法列表打印
发现此时是无序的,下面是排序后的方法打印
markdown
咱们看到方法名的地址是从小到大进行排列的。这里不是经过imp排列的。数据结构
咱们看下SortBySELAddress方法 咱们发现SortBySELAddress是跟name相关联的,在fixupMethodList方法里,只有sel_registerNameNoLock是跟name相关联的。下面咱们看下sel_registerNameNoLock方法实现
多线程
红框内的方法基本不走,除非进行sel的alloc开辟空间。函数
咱们看下search_builtins方法的具体实现 关键信息就是_dyld_get_objc_selector,这个方法就是在dyld源码里了,咱们去dyld源码看看
post
若是当前的类存在就去共享缓存去拿,若是是dyld3就在执行一次。这是由于系统的方法会在共享缓存内,咱们本身写的方法是不在共享缓存内的,因此才会有若是存在就去共享缓存中去拿。因此咱们本身写的方法就必须走dyld3。ui
咱们继续向下研究
atom
1362-1364行就是判断是否为空地址,若是为空就返回。若是不是就拿到imageAndOffset,而target是从selName拿到的,而后赋值给imageAndOffset.raw。1731行就是当前段的首地址进行偏移。因此这个值是不断的变化的。spa
经过上面能够知道方法排序显示同名方法排序,然后是按照方法名的地址大小进行排序的。线程
先看下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:类别,分类
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。到此结束。 画了张流程图关联对象的
上面讲了文章开头说的几个问题。重点是关联对象,这里咱们在对关联对象进行总结:
关联对象:设值流程
关联对象插入空流程
关联对象:取值流程