docs: clang 插件开发

This commit is contained in:
杭城小刘
2024-04-27 13:01:58 +08:00
parent 6e47061735
commit 851797d133
257 changed files with 9060 additions and 239 deletions

View File

@@ -2,6 +2,8 @@
> 很多人都知道类别、分类的用法但是对于一些细节就不是很清楚了本文主要梳理下这3个概念的细节
## 类别Category
### 文件特征
@@ -59,48 +61,60 @@
5. Category 的作用是向下有效的。即只会影响到该类的所有子类。比如 A 类和 B 类是继承自 Super 类的2个子类当给 A 类添加一个 Category sayHello 方法仅有A 类的子类才可以使用 sayHello 方法
## Category 底层原理
### Category 是 category_t 结构体
Demo
来一个简单的 Person 类,为其添加一个 Category增加一些属性和类方法、对象方法、遵循协议
```objectivec
// Person
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sayHi;
- (void)sleep;
@end
NS_ASSUME_NONNULL_END
// Person.m
#import "Person.h"
@implementation Person
- (void)sayHi{
NSLog(@"Person sayHi");
- (void)sayHi {
NSLog(@"Hello world");
}
- (void)sleep{
NSLog(@"人生无常,抓紧睡觉");
NSLog(@"Time to slepp");
}
@end
@end
// Person+Study.h
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (Study)<NSCopying>
@property (nonatomic, strong) NSString *no;
@property (nonatomic, assign) NSInteger score;
- (void)study;
+ (void)sleep;
@end
NS_ASSUME_NONNULL_END
// Person+Study.m
#import "Person+Study.h"
@implementation Person (Study)
- (void)study{
- (void)study {
}
+ (void)sleep{
+ (void)sleep {
NSLog(@"Time to sleep");
}
- (void)setNo:(NSString *)no{
- (void)setScore:(NSInteger)score {
}
- (NSString *)no{
return nil;
- (NSInteger)score {
return 100;
}
- (id)copyWithZone:(NSZone *)zone{
return self;
@@ -108,153 +122,237 @@ Demo
@end
```
clang 转为 c++ 代码
clang 转为 c++ 代码,具体指令为 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc Person+Study.m`
查看到 Category 本质是一个结构体
```c
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
查看 `Person+Study.cpp` 文件,可以看到 Category 本质是一个结构体
```c++
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"study", "v16@0:8", (void *)_I_Person_Study_study},
{(struct objc_selector *)"setScore:", "v24@0:8q16", (void *)_I_Person_Study_setScore_},
{(struct objc_selector *)"score", "q16@0:8", (void *)_I_Person_Study_score},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_Study_copyWithZone_}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"sleep", "v16@0:8", (void *)_C_Person_Study_sleep}}
sizeof(_objc_method),
1,
{{(struct objc_selector *)"sleep", "v16@0:8", (void *)_C_Person_Study_sleep}}
};
static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"@24@0:8^{_NSZone=}16"
"@24@0:8^{_NSZone=}16"
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};
```
可以看到 Person+Study 的Category 底层赋值代码如下,就是结构体对象的初始化。
struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0,
"NSCopying",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"score","Tq,N"}}
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;
```c
static struct _category_t _OBJC_$_CATEGORY_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Study,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Study,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Study,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Study,
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Study,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Study,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Study,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Study,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_Study(void ) {
_OBJC_$_CATEGORY_Person_$_Study.cls = &OBJC_CLASS_$_Person;
}
```
可以看到 `Person+Study` 的 Category 底层赋值代码如下,就是结构体对象的初始化(参考上面的结构体各个成员变量)
```c++
static struct _category_t _OBJC_$_CATEGORY_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Study,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Study,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Study,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Study,
};
```
`_OBJC_CATEGORY_INSTANCE_METHODS_Person__Study` 结构体存放的是对象方法信息,如下
`_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Study` 结构体存放的是对象方法信息,如下
```c
```c++
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"study", "v16@0:8", (void *)_I_Person_Study_study},
{(struct objc_selector *)"setNo:", "v24@0:8@16", (void *)_I_Person_Study_setNo_},
{(struct objc_selector *)"no", "@16@0:8", (void *)_I_Person_Study_no},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_Study_copyWithZone_}}
sizeof(_objc_method),
4,
{{(struct objc_selector *)"study", "v16@0:8", (void *)_I_Person_Study_study},
{(struct objc_selector *)"setScore:", "v24@0:8q16", (void *)_I_Person_Study_setScore_},
{(struct objc_selector *)"score", "q16@0:8", (void *)_I_Person_Study_score},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_Study_copyWithZone_}}
};
```
`_OBJC_CATEGORY_CLASS_METHODS_Person__Study` 结构体存放的是类方法信息,如下
`_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Study` 结构体存放的是类方法信息,如下
```c
```c++
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"sleep", "v16@0:8", (void *)_C_Person_Study_sleep}}
sizeof(_objc_method),
1,
{{(struct objc_selector *)"sleep", "v16@0:8", (void *)_C_Person_Study_sleep}}
};
```
`_OBJC_CATEGORY_PROTOCOLS*_Person__Study` 结构体存放的是遵循的协议信息,如下
`_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Study` 结构体存放的是遵循的协议信息,如下
```c
```c++
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
1,
&_OBJC_PROTOCOL_NSCopying
};
```
`_OBJC_PROP_LIST_Person__Study` 存放的是 Category 中的属性信息,如下
`_OBJC_$_PROP_LIST_Person_$_Study` 存放的是 Category 中的属性信息,如下
```c
```c++
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Study __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
sizeof(_prop_t),
1,
{{"no","T@\"NSString\",&,N"}}
1,
{{"score","Tq,N"}}
};
```
查看 Objc 4 源代码Category 定义如下
查看 [Objc 4 源代码](http://opensource.apple.com/tarballs/objc4/)Category 定义如下
```c
```c++
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
method_list_t *methodsForMeta(bool isMeta) const {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi) const;
protocol_list_t *protocolsForMeta(bool isMeta) const {
if (isMeta) return nullptr;
else return protocols;
}
};
```
结论:
- 为某个类添加的分类,分类中可能有属性、对象方法、类方法、遵循的协议、协议方法,本质都是存储到 `category_t` 结构体里面。
- 当有多个分类的时候,是通过分类数组来承载的
### category 中定义的方法,存储在哪?
查看 objc4 的源代码 `objc-os.mm` 文件中的 `_objc_init` 方法
抛个问题:当对象调用方法的时候,不管这个方法是类自身的方法,还是通过分类添加的方法,本质都是通过 isa 指针去寻找方法实现,(如果是对象方法,则通过 instance 的 isa 去找到类对象,最后找到对象方法的实现去调用;如果是类对象方法,则通过 class 的 isa 找到元类对象,最后找到类方法的实现进行调用),那给 Category 添加的方法,是「**如何“塞到”类对象或者元类对象的方法列表中去的**」?
```c
带着问题查看 [objc4 的源代码](http://opensource.apple.com/tarballs/objc4/) `objc-os.mm` 文件中的 `_objc_init` 方法
```c++
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void) {
static bool initialized = false;
if (initialized) return;
@@ -271,13 +369,24 @@ void _objc_init(void) {
`_objc_init` 内部会调用 `map_images` 方法,其内部如下
```c
```c++
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[]) {
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
```
`map_images` 内部会调用 `map_images_nolock`
```c++
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[]) {
static bool firstTime = YES;
@@ -418,9 +527,9 @@ void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
}
```
`map_images` 内部会调用 `map_images_nolock` `map_images_nolock` 会调用 `_read_images`
`map_images_nolock` 会调用 `_read_images` 方法,如下:
```c
```c++
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
@@ -823,7 +932,7 @@ static void remethodizeClass(Class cls){
可以看到内部调用 `attachCategories` 方法。 `attachCategories` 方法传入 3个参数第一个是类对象Person第二个参数是 Category 数组。内部实现如下
```c
```c++
static void attachCategories(Class cls, category_list *cats, bool flush_caches){
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
@@ -878,7 +987,9 @@ static void attachCategories(Class cls, category_list *cats, bool flush_caches){
}
```
可以看到通过传入的类对象 `cls` 调用其 `cls->data()` 方法,找到对应的类 `class_rw_t` 信息,里面存放方法、属性、协议信息
观察到采用 `i--` 的方式,当 while 循环结束的时候,方法数组 `mlists` 保存了全部分类中的方法,属性数组 `proplists` 保存了全部分类中的属性,协议数组 `protolists` 保存了所有分类所遵循的协议
可以看到通过传入的类对象 `cls` 调用其 `cls->data()` 方法,找到对应的类 `class_rw_t` 信息,里面存放:方法列表、属性列表、协议列表信息。
```c
// 类对象结构体
@@ -944,34 +1055,56 @@ void attachLists(List* const * addedLists, uint32_t addedCount) {
其中关键函数 `memmove` 代表将 __src 中的前 __len 个字节长度移动到 __dst 中去。
```c
```c++
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
```
等价于
```c++
memmove(类对象原来的方法列表 + addedCount,
类对象原来的方法列表,
oldCount * sizeof(array()->lists[0]))
memcopy(类对象原来的方法列表,
所有分类的方法列表,
addedCount * sizeof(array()->lists[0]))
```
其中,`array()->lists` 代表类对象原来的方法列表、`oldCount * sizeof(array()->lists[0])` 代表类对象原来方法列表长度,`addedCount` 代表 category 方法列表长度。
c 数组指针 `array()->lists + addedCount` 可以代表其中的位置。
`memmove` 效果为将类原方法列表移动到第 n个n为 category 方法列表长度位置前面空出n个坑位
`memmove` 的效果是,将类原来的方法列表移动到第 n个n为 category 方法列表长度位置前面空出n个坑位,预留坑位给所有分类的方法)
`memcopy` 效果将 category 方法列表拷贝到类原方法列表的前面去。位置刚好是 `memmove` 留出的坑位。
`memcopy` 效果将 Category 方法列表拷贝到类原方法列表的前面去。位置刚好是 `memmove` 留出的坑位。
过程如下
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/runtime-categoryattachLists.png)
QA
结果就是类方法列表中,最前面的就是所有分类的方法列表,最后是类自身的方法列表。
分类中可以写属性吗?
总结:
不可以,查看分类的 category_t 结构体可以看到没有 `**const** ivar_list_t * ivars;` ,所以 category 声明属性底层只会生成 setter、getter 方法声明,没有实现。需要程序员利用 runtime 关联属性自己实现
Category 编译之后 底层结构为 struct category_t里面存储着分类的对象方法、类方法、属性、协议信息
程序运行的时候runtime 会将 Category 中的数据,合并到类自身信息中(类对象、元类对象)
### QA
#### 分类中可以写属性吗?
不可以。从源码角度来讲,查看分类的 category_t 结构体可以看到没有 `const ivar_list_t * ivars;` ,所以 category 声明属性底层只会生成 setter、getter 方法声明,没有实现。需要程序员利用 runtime 关联属性自己实现
同理,分类中也不可以添加成员变量,下面代码会报错。
```
```objective-c
@interface Person (Study)<NSCopying>
{
int _age;
@@ -979,11 +1112,85 @@ QA
@end
```
总结:
Category 编译之后 底层结构为 struct category_t里面存储着分类的对象方法、类方法、属性、协议信息
程序运行的时候runtime 会将 Category 中的数据,合并到类自身信息中(类对象、元类对象)
从代码设计角度来讲,假设一个 Person 类只有1个 `_age` 成员变量,其内存布局在编译阶段就可以确定。内存布局大概为:
```objective-c
struct Person_IMPL {
Class isa;
int _age;
}
```
但 Category 是苹果利用 Runtime 实现的,是运行期动态修改 `class_rw_t` 决定的。
#### 为什么分类中的方法需要放在类自身方法列表的开头?
查看源码为什么分类中的方法需要放在类自身方法列表的开头?因为需要优先保证 Category 中的方法优先被调用。
#### 分类中存在同名方法存在什么问题
对象调用方法的时候会根据对象的 isa 指针,找到类对象方法列表,然后查找方法,由于分类方法在方法列表的前面,类自身方法在方法列表的后面,所以当优先找到分类方法实现的时候就停止查找了,给人的感受就是,方法”被覆盖了 “
Demo: 为 Person 类创建2个 Category分别存在同名方法 study具有不同实现。在控制器的手势事件中打印方法列表
```objective-c
- (void)displayMethodName:(Class)cls {
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
free(methodList);
NSLog(@"className: %@, methodNames: %@", NSStringFromClass(cls) , methodNames);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [[Person alloc] init];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.p study];
[self displayMethodName:[Person class]];
}
```
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryMethodOrderExplore.png" style="zoom:25%">
可以看到 sayHi 方法存在多个,但是由于 Category 同名的方法在方法列表的前面,所以类自身的方法实现”被覆盖了“(根据 isa 查找方法实现的时候,优先查找到 Category 的方法实现,则停止查找了)
#### 2个分类存在同名方法谁先调用
- 分类方法优先级高于类自身方法
- 同样是分类方法由编译顺序决定哪个方法会被调用XcodeBuild Phases -> Compile Sources编译顺序越后面的方法优先被调用
Demo: 为 Person 类创建2个 Category分别存在同名方法 study具有不同实现。探索编译顺序决定方法实现
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryBuildOrderDemo1.png" style="zoom:25%">
2个对比实验
让 `Person+Study` 参与后编译
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryBuildOrderDemo2.png" style="zoom:25%">
让 `Person+Learn` 参与后编译
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryBuildOrderDemo3.png" style="zoom:25%">
## 拓展Extension
@@ -1224,7 +1431,9 @@ void attachLists(List* const * addedLists, uint32_t addedCount) {
- 合并后的 Category 数据(属性、方法、协议)插入到类原来数据的前面(比如class_rw_t->methods)
## 小插曲:为 Category 实现属性的 Setter 和 Getter
## 为 Category 实现属性的 Setter 和 Getter
```objectivec
#import "Person.h"
@@ -1317,7 +1526,9 @@ NS_ASSUME_NONNULL_END
@end
```
## 底层窥探 load 方法
## 底层剖析 load 方法
Demo 验证。
@@ -1357,14 +1568,28 @@ Student *st = [[Student alloc] init];
QA:
- 为什么 load 方法打印顺序是这样的
- 为什么 `initialize`方法存在“覆盖”的情况
因为调用 student alloc相当于发送了消息。则肯定先执行 load 方法。类在 Runtime 启动阶段会调用 `schedule_class_load` 方法。方法内部递归调用,如果当前类存在父类则递归调用,否则将当前类加载到 loadable_classes 最后面。load 方法在本质上是执行 `call_load_methods`,方法地址是确定的。不走 objc_msgSend 这套流程。所以先打印父类 load、再打印子类 load、最后打印分类 load。如果存在多个分类则按照编译顺序打印 load
就是打印了 Student Category 的 `initialize`,却没有打印 Student 自身的 `initialize`
查看 objc 源码发现调用 `initialize` 方法本质上就是通过 runtime 的 `objc_msgSend ` 发消息来实现的。也就是通过 isa 指针查看类对象或元类对象的方法列表中(方法列表中包含当前类各个 Category 的各个方法)查找方法实现。所以当找到方法列表中排列较前的 `initialize` 时,就不再继续查找方法实现了。也就出现了 `initialize` 被覆盖的情况了。
```c++
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
```
- 为什么 load 方法打印顺序是这样的?
因为调用 student alloc相当于发送了消息。则肯定先执行 load 方法。类在 Runtime 启动阶段会调用 `schedule_class_load` 方法。方法内部递归调用,如果当前类存在父类则递归调用,否则将当前类加载到 loadable_classes 最后面。load 方法在本质上是执行 `call_load_methods`,方法地址是确定的(查看下面的源代码可以发现 `load` 方法是在编译期就可以确定的)。不走 `objc_msgSend` 这套流程。所以先打印父类 load、再打印子类 load、最后打印分类 load。如果存在多个分类则按照编译顺序打印 load。
- 为什么 load 方法不是按照 Category 编译顺序倒序调用 load 方法?
看源代码 Objc4
```c
void _objc_init(void){
static bool initialized = false;
@@ -1525,15 +1750,15 @@ QA:
return new_categories_added;
}
```
会发现源码中先调用类的 load 方法,再调用 category 的 load 方法。
再看看 `call_class_loads`、`call_category_loads` 方法内部实现,是直接找到 `load_method_t load_method = (load_method_t)classes[i].method;` 类对象的 load 方法地址。最后直接调用 `(*load_method)(cls, SEL_load);` 方法本身。
test 方法是走消息发送流程 `objc_msgSend()` 所以会走 isa、superclass 这一套流程test 是对象方法,所以需要根据 isa 找到类对象,从类对象的对象方法列表找到 test 方法,找不到则根据 superclass 找到当前类对象的父类对象,继续查找方法列表。直到 NSObject、nil 对象为止,然后走消息起死回生的阶段。
看2个结构体
```c
struct loadable_class {
Class cls; // may be nil
@@ -1694,7 +1919,9 @@ Extension 在编译阶段,数据已经包含在类信息中。
Category 是在运行阶段,才会将数据合并到类信息中。
## 底层窥探 Initialize 方法
## 底层剖析 Initialize 方法
上 Demo
@@ -1724,19 +1951,18 @@ Category 是在运行阶段,才会将数据合并到类信息中。
+initialize 和 +load 最大区别是 +initialize 是通过 objc_msgSend 进行调用的
`+initialize``+load` 最大区别是 `+initialize` 是通过 `objc_msgSend` 进行调用的
- 调用方式load 根据函数地址直接调用initialize 是根据 objc_msgSend 调用的
- 调用方式:`load` 根据函数地址直接调用,`initialize` 是根据 `objc_msgSend` 调用的
- 调用时刻load 是 runtime 加载类、分类的时候调用的。initialize 是在类第一次接收消息的时候调用的。每个类只会 initialize 一次,但是父类的 initialize 可能会调用多次
- 调用顺序:
- load先调用类的 load先编译的类优先调用 load、调用子类的 load会先调用父类的 load、再调用分类的 load先编译的分类优先调用 load
- 如果子类没有实现 +initialize 则会调用父类的 +initialize所以父类的 +initialize 可能会被调用多次)
- 如果分类实现了 +initialize就会覆盖类本身的 +initialize 调用
- 如果子类没有实现 `+initialize` 则会调用父类的 `+initialize`(所以父类的 `+initialize` 可能会被调用多次)
- 如果分类实现了 `+initialize`,就会覆盖类本身的 `+initialize` 调用
查看源码,伪代码如下: