类的加载原理:
iOS 类的加载原理上
iOS 类的加载原理中
iOS 类的加载原理下
分类的加载原理补充及类扩展 , 关联对象介绍
分类加载的补充
method_list 数据结构
首先通过源码可以看到 method_list_t
继承于 entsize_list_tt
,包含 method_t
, method_list_t
, 0xffff0003
这三个元素。
我们在 attachCategories
函数中输出打印可以看到 method_list
是一个指针类型,也可以看到 method_list
的数据结构,我们通过 get(0)
可以获取到 method_t
。
我们查看 entsize_list_tt
也可以看到有一个 get
方法,并且 method_list_t
里面存储的是指针而不是 method_t
相应的数据结构。
主类没有实现 load 方法,分类实现了 load 方法,数据是如何加载的
前面我们在 iOS 类的加载原理下 中分析了分类加载的几种情况,这里再补充一种,当主类不实现 load
方法,分类大于 1 个,且都实现 load
方法的时候最后会执行 attachCategories
方法。这里我们来分析一下具体的执行流程。
-
load_images
首先不一样的点是在 load_images
函数中没有走 loadAllCategories
方法,而是执行了 prepare_load_methods
。
prepare_load_methods
这里会迫使主类执行 realizeClassWithoutSwift
流程。
-
methodizeClass
-
attachToClass
这里会执行到 else
里面,因为从 realizeClassWithoutSwift
方法来到这里,就可以确定要么是元类,要么是本类。
attachCategories
这里就会进行方法的处理,跟正常方法的加载处理逻辑一样。这里需要补充一点,当一个分类分类里面没有任何方法,这里 cats_count
不会计入。
分类的加载是否需要排序
当分类的加载方法列表是否需要排序,这里需要分两种情况,同样我们在 iOS 类的加载原理下 中讲的,当类与分类都实现 load
方法的时候 methodList
中存放的是方法数组指针,分类 A 方法数组指针 + 分类 B 方法数组指针 + 主类方法数组指针
(这里分类的先后跟加载顺序有关),通过前面讲的,我们也知道,这里会经过排序。
在这种情况下当分类与主类都实现一个方法 saySomething
的时候,我们来看一下源码的查找流程。
在 getMethodNoSuper_nolock
函数中我们输出 methods.beginLists()
可以看到这里取出的是分类的方法数组指针,也可以看出 methods
存放的是方法数组指针,这里会循环遍历 methods
,然后取出 mlists
,然后查找 mlists
中是否有对应的 method_t
,有的话就返回。
上面讲了分类与主类都实现 load
方法的情况,其他情况下分类与主类的方法会一次通过 data()
方法获取到,method_list_t
中存放的不再是数组指针,而是 method_t
指针,分类与主类的方法都会存放在 method_list_t
中。
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// base相当于low地址,count是max地址,probe是middle地址
for (count = list->count; count != 0; count >>= 1) {
// 指针平移至中间位置
// 从首地址 + 下标 --> 移动到中间位置(count >> 1)
probe = base + (count >> 1);
// 获取该位置的sel名称
uintptr_t probeValue = (uintptr_t)getName(probe);
// 如果查找的key的keyvalue等于中间位置(probe)的probeValue,则直接返回中间位置
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 分类方法同名- while 平移 -- 向前在查找,判断是否存在相同的方法,保证调用的是分类的
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
// 如果keyValue 大于 probeValue,就往probe即中间位置的右边查找,即中间位置再右移
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
当方法查找的时候会在 findMethodInSortedMethodList
函数中会通过二分查找的方式进行查找,保证找到 method_list_t
中最前面的一个方法。
class_ro_t 数据结构
我们前面讲的在 realizeClassWithoutSwift
中执行 auto ro = (const class_ro_t *)cls->data()
这句代码就能得到 class_ro_t
这样的数据结构,并对 class_ro_t
中的属性进行赋值。那么这里是如何赋值的呢?
首先 realizeClassWithoutSwift
在 _read_images
函数中执行,所以在编译阶段就能读取到这些数据,所以 class_ro_t
不是在 objc
源码中,而是在 LLVM
源码中(gitHub 上可以下载到,建议用 VSCode 打开)。
在 LLVM
源码中我们可以看到 class_ro_t
的数据结构跟在 objc
源码中看到的类似,只是多了 m_
的前缀。
然后我们来到 Read
方法,这里会先读取当前的 addr
,然后经过一些包装处理得到 extractor
,然后分别执行 extractor .GetU32_unchecked(&cursor)
对结构体的属性进行赋值。在 objc
源码中是通过类型强转进行接收的。
在 Read_class_row
方法中可以看到在这里进行了 Red
方法的调用。其实 method_list_t
跟 objc_class
的读取也是类似。
类扩展分析
类扩展与分类的区别。
-
category
: 类别,分类
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员变量,也无法取到(注意:其实可以通过
runtime
给分类添加属性) - 分类中用
property
定义变量,只会生成变量的getter
,setter
方法的声明,不能生成方法实现和带下划线的成员变量。
-
extension
: 类扩展
- 可以说成是特殊的分类,也称作匿名分类
- 可以给类添加成员属性,但是是私有变量
- 可以给类添加方法,也是私有方法
类扩展底层代码实现
首先我们在 main.m
文件中定义一个 LGPerson
类,并在类扩展中添加属性与方法。然后通过 clang -rewrite-objc main.m
生成 cpp
文件来看一下底层 c++
代码的实现。
@interface LGStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface LGStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGStudent
- (void)instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)classMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson * person = [LGPerson alloc];
[person saySomething];
}
return 0;
}
生成 cpp
文件之后我们可以看到在类扩展里面添加的方法 ext_instanceMethod
一样被加载到 method_list
中来了。
类扩展是否影响类的加载和编译
前面我们讲分类的时候知道分类会影响类的加载和编译,那么类扩展是否也会影响呢?这里我们为 LGPerson
类添加类扩展并添加方法,然后执行源码并在 realizeClassWithoutSwift
函数中打印 ro
来看一下。
@interface LGPerson ()
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGPerson
+ (void)load{}
- (void)saySomething{
NSLog(@"%s",__func__);
}
+ (void)sayHappy{
NSLog(@"LGPerson say : Happy!!!");
}
- (void)sayHello1{
NSLog(@"sayHello1 %s", __func__);
}
+ (void)say6661{
NSLog(@"say6661 %s", __func__);
}
- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
我们在这里不断 p $1.get(n).big()
,当 p $1.get(2).big()
的时候可以看到输出了类扩展中添加的方法 ext_instanceMethod
,可以看出类扩展中数据会作为类的一部分被一块加载。
关联对象
- (void)setCate_name:(NSString *)cate_name{
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
我们通常通过 runtime
的 objc_setAssociatedObject
方法来添加分类的关联对象,那么我们来看一下 objc_setAssociatedObject
方法的底层是如何实现的(这里是 779 版本)。
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
通过底层源码可以看到这里会调用 _object_set_associative_reference
方法,不同版本的源码这里实现会有不同,但是最上层都是调用 objc_setAssociatedObject
方法,这里就提现了苹果的分层思想,也是值得我们学习的,这里就保证了最上层 api
的稳定性。
接着我们继续分析 _object_set_associative_reference
方法。
首先我们先看下 _object_set_associative_reference
方法的简单注释及这张结构体图。通过结构图我们可以看到,ObjcAssociation
跟 ObjcAssociation
会被包装成 ObjcAssociation
的形式,然后通过键值匹配的形式被存储到 ObjectAssociationMap
,每个对象会对应一张 ObjectAssociationMap
表,最后每个对象跟对应的ObjectAssociationMap
又会以键值的形式存到 AssociationsHashMap
,这里是双层 hashMap
结构。AssociationsHashMap
是一张全局唯一的表。具体原因我们可以看下 AssociationsManager
。
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
// _mapStorage 是一个静态变量,写到这里代表只有 AssociationsManager 才能调起 _mapStorage
// 所以不同的 AssociationsManager 这里调用的都是同一个 _mapStorage
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
// 所以得到的 AssociationsHashMap 是唯一的
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
通过打印我们也可以看到 AssociationsHashMap
及 refs_result
的数据结构。这里我们大致了解了关联对象的数据结构,那么 key
, value
具体是如何存储的呢?我们接着继续往下看。
if (value) {
// 第一次调用的时候 try_emplace 会创建一个空的 TheBucket
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
// 第二次再执行的时候会对 TheBucket 的 value 赋值为 association
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
// 如果 value 为空这里就会清空
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
// 这里会先创建一个空的 BucketT
BucketT *TheBucket;
// 这里 key 是包装的对象,被关联对象,会判断能不能找到对应的 TheBucket
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// 找不到就会来到这里会插入一个空的 TheBucket
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
最后总结一下关联对象的设值流程:
关联对象:设值流程
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:其实如果插入空置相当于清除
网友评论