iOS 底层探索篇 ——类的加载原理补充,类拓展和关联对象
1.分类加载是否需要排序
由上一篇文章知道,在方法列表中,2个分类的方法的数组指针是在主类之前的,并且数组指针里面是经过排序的。 首先添加2个分类,并且类和2个分类都有一个方法叫做testfunc1 。 运行,看到getMethodNoSuper_nolock 里面。看到这里是遍历查找 。 输出看到beginLists 是分类LS 。 array_t 里面存的是3个2维指针 ,这个指针没有必要进行排序,他们各自有各自的排序。取methodList的时候进行了遍历查找,也说明了分类加载不需要进行排序。 那么2分查找的时候需要进行probe-- 呢?因为当类和分类都在data数据段 被读取的时候,方法整合到一块 了,所以需要probe–去找到最前面 的那个方法。
2.methodList的数据结构
methodList的数据结构是method_list_t ,继承自entsize_list_tt 。 从上篇文章中可以知道,methodList 是以指针方式存储的。 而当需要获取其中的元素时候,需要调用get 方法来获取,这也说明了method_list_t里面存的也是指针而不是值 ,否则的话是可以以数组的方式取出来的。而指针则指向了method_t 。 在看到get方法,可以看出get方法是通过地址平移 来获取到指针地址的。
3.主类没有实现load方法,分类实现load方法时的数据加载
当主类没有实现load方法,那么分类时候load方法时候情况会是什么样的呢? 首先创建3个分类,其中2个分类里面有方法和属性,剩下一个没有 。 在有方法和属性的分类中,选择一个实现load方法 。 运行后发现主类非懒加载 ,但是分类都没有加载 。 两个分类都实现load方法 ,运行后可以发现分类加载进去了 。 这里可以得出,当2个或以上的分类实现了load方法,那么分类就会进行非懒加载。 在看到attachCategories 的时候,cats_count为2 ,那么是哪个分类没有加载进去呢?
输出来看一下哪个分类没有加载。这里看到LS 和LSS 加载了,LSSS 也就是没有方法和属性的分类没有加载,说明了系统做了优化,当分类什么都没有的时候,就不会被加载浪费性能。
在看到load_images 里面,发现走的是prepare_load_methods 而不是正常的loadAllCategories 方法。prepare_load_methods 也就是迫使主类实现的地方, 看到里面的prepare_load_methods 的实现,发现确实让主类进行了非懒加载,也就是让主类被迫营业了。
4.class_ro_t
在llvm看一下class_ro_t。 看到这里有个read ,查找一下read。看到read里面进行了地址的处理,然后获得了extractor ,然后进行了各个属性进行了赋值 。
5.类扩展
分类:categoty
- 专门用来给类添加新方法。
- 不能给类添加成员属性,添加了成员变量也无法取到,但是可以通过runtime给分类添加属性。
- 分类中用@property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法实现和带下划线的成员变量。
扩展:extension
- 特殊的分类,也叫做匿名分类。
- 可以给类添加成员属性,但是是私有变量。
- 可以给类添加方法,也是私有方法。
这就是扩展,其必须定义在@interface 和 @implementation 之间 。 clang 一下 main.m 后打开。发现拓展的方法以及属性已经存在了 那么类扩展是否会和分类那样有一个自己的结构体呢?找了一圈发现没有。分类会影响到类的加载 ,那么拓展会吗? 为LGPerson 添加一个类扩展并添加方法,然后在LGPerson中添加方法的实现。LGPerson实现load 方法,在realizeClassWithoutSwift 打下断点然后运行。 运行后在realizeClassWithoutSwift输出ro的方法列表然后观察,发现类扩展已经被加载进去了。也就是说,在类加载的时候,类扩展作为类的一部分已经加载进去了。
6.关联对象
关联对象的底层原理的实现,主要分为两部分:
- 通过
objc_setAssociatedObject 设值流程 - 通过
objc_getAssociatedObject 取值流程
在分类LGA 中重写属性cate_namet 、cate_age 的set 、get 方法,通过runtime 的属性关联方法也就是objc_setAssociatedObject 和objc_getAssociatedObject 实现。
关联对象设值流程
看objc_setAssociatedObject 的具体实现。这种设计模式属于是接口模式 ,这种模式下对外的接口不变,也就是外部调用永远是调用objc_setAssociatedObject,内部的逻辑变化不影响外部的调用 。
然后来到_object_set_associative_reference 里面。看到这里有四个参数。
- objc:要关联的对象,即给谁添加关联属性
- key:标识符,方便下次查找
- value:要存的值
- policy: 关联策略
往下看,看到了对object 进行了处理。 点进去查看disguise 方法,对ptr 进行了处理,也就是value 的处理,所以这里就是对object 包装了一下,包装成统一的数据结构 。 在往下是ObjcAssociation association{policy, value} 。
这里的const void * 也就是属性的名字,比如说cate_name,cate_age,这里是键值匹配。而在AssociationsHashMap 中,不同对象也对应不同的AssociationsMap,所以就是双层HashMap结构 。 运行一下程序,来到_object_set_associative_reference 里面。 输出一下确实是我们设的值。 其中,AssociationsManager manager 是一个构造函数。模仿着写一个以便更好的理解。
运行后发现输出了 KC 来了 和大师班 NB 。 所以AssociationsManager manager 相当于调用了 Ass ociationsManager() 和~AssociationsManager() 。 在看到AssociationsHashMap 其实是获取_mapStorage ,而_mapStorage是静态变量 ,所以AssociationsHashMap这张表是单例,是唯一的 。 回到_object_set_associative_reference 往下走,输出associations ,发现里面还没有值。 再输出refs_result ,看到second 为true ,那么就会把 、isFirstAssociation 设为 true,看到注释也写着 it’s the first association we make,也就是当第一次关联的时候会把 isFirstAssociation设为true 。
refs_result是从associations.try_emplace(disguised, ObjectAssociationMap{}) 来的,看一下try_emplace 方法。看到这里会创建新的桶子 ,这里的key是LGPerson 的地址 。 首先会进入LookupBucketFor 去查找是有已经有了桶子,看到两个实现,因为在try_emplace中是BucketT没有 const ,所以走的是下面的实现。下面的实现会调用上面的实现。 看上面的实现这里是得到了哈希的下标 。 往下就开始死循环找bucket,没找到就在哈希。 当第一次进来的时候,LookupBucketFor(Key, TheBucket) 是找不到的,所以走到了下面。这里就插入一个空的桶子进去,并且还进行了3/4扩容2倍 的操作。
然后就得到了 TheBucket _object_set_associative_reference 往下走,又进入了一次try_emplace 。 进去后发现LookupBucketFor(Key, TheBucket) 还是没有进去,这是因为 这时候bucket里面还没有东西插进去,如果是空的桶子,LookupBucketFor 也会返回false 。 这里的又会走到InsertIntoBucket ,然后在InsertIntoBucket 里面将值插入到first 里面
在_object_set_associative_reference 中输出result ,发现first中有了值。 而first里面的ptr 则是我们存进去的bucket 。 而当传一个空 的值的话,那么就会把这个桶子清除 。
关联对象: 设值流程总结
1: 创建一个 AssociationsManager 管理类 2: 获取唯一的全局静态哈希Map 3: 判断是否插入的关联值是否存在: 3.1: 存在走第4步 3.2: 不存在就走 : 关联对象插入空流程 4: 创建一个空的 ObjectAssociationMap 去取查询的键值对 5: 如果发现没有这个 key 就插入一个 空的 BucketT进去 返回 6: 标记对象存在关联对象 7: 用当前 修饰策略 和 值 组成了一个 ObjcAssociation 替换原来 BucketT 中的空 8: 标记一下 ObjectAssociationMap 的第一次为 false
关联对象插入空流程总结
1: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器 2: 清理迭代器 3: 其实如果插入空置 相当于清除
关联对象流程图。
图片来自链接: Style_月月.
|