OC底层原理之-类加载部分问题&关联对象底层原理

前面将类的加载说完了,下面说几个问题,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格式怎么来的

先看下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
相关文章
相关标签/搜索