创建一个Category 分类
- 简单调用和实现
- 生成一个cpp文件
- 关闭cpp文件参与编译
- 生成一个这样的结构体 有几个分类就会在编译时生成几个结构体对象。
- 源码中的定义
-
instance_methods 对象方法实例方法
-
class_methods 类方法
-
protocol 协议
-
properties 属性
-
存储的数据
-
源码核心代码区
-
添加方法、协议、成员属性
- attachLists 源码分析
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
- 取出老数组的数量+新数组数量
- 创建新的数组 数组扩容
- 现在分析一下这里的结构 原来用的时一个函数 memmove 显然新的源码已经没有了
- 1.假设oldCount = 1,addedCount = 2,newCount = 3
- 2.扩容成了数量为3的新数组 下标 0,1,2,
- 3.开始对oldCount的for循环 oldCount=1 就循环了一次
- 4.array()->lists[i] 取出数据 i = 0,可以得知取数据也是从后面还是去的,因为加入odlCount = 2,i=1,取数据也是从下标大的开始的。
- 5.注意这个时候加到新数组的下标是 i+addedCount = 2,所以原来old的就放到了下标2处的位置
- 6.循环addedCount的时候就是正常的0++,下标0和下标1就一次存放了
- 7.所以得出结论调用了这个方法后,原来的方法,协议,成员属性都会被放到数组的后面了
-
分类1先参与的编译运行时将原先方法挤到后面去,优先在类对象的方法列表中找到分类1的方法。
-
分类2再参与编译运行时,原先的1就变成了old了,分类2又把分类1的方法挤到后面去了
-
控制编译顺序
-
objc_msgSend
Category的加载处理过程
-
1.通过Runtime加载某个类的所有Category数据
-
2.把所有Category的方法、属性、协议数据,合并到一个大数组中
- 后面参与编译的Category数据,会在数组的前面
-
3.将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
-
源码解读顺序
-
objc-os.mm
- _objc_init
- map_images
- map_images_nolock
-
objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
-
load
- load 方法是直接获取方法的地址进行调用的
-
+load方法会在runtime加载类、分类时调用
-
每个类、分类的+load,在程序运行过程中只调用一次
-
调用顺序
-
先调用类的+load
-
按照编译先后顺序调用(先编译,先调用)
-
调用子类的+load之前会先调用父类的+load
-
再调用分类的+load
-
按照编译先后顺序调用(先编译,先调用)
-
-
objc4源码解读过程:objc-os.mm
-
_objc_init
-
load_images
-
prepare_load_methods
-
schedule_class_load
-
add_class_to_loadable_list
-
add_category_to_loadable_list
-
call_load_methods
-
call_class_loads
-
call_category_loads
-
(*load_method)(cls, SEL_load)
-
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
initialize
-
+initialize方法会在类第一次接收到消息时调用
-
调用顺序
- 先调用父类的+initialize,再调用子类的+initialize
- (先初始化父类,再初始化子类,每个类只会初始化1次)
-
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
- 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
- 如果分类实现了+initialize,就覆盖类本身的+initialize调用
-
objc4源码解读过程
-
objc-msg-arm64.s
-
objc_msgSend
-
objc-runtime-new.mm
- class_getInstanceMethod
- lookUpImpOrNil
- lookUpImpOrForward
- _class_initialize
- callInitialize
- objc_msgSend(cls, SEL_initialize)
-
-
源码方法
-
先判断父类有没有初始化 递归调用
-
完成自己的初始化
-
消息发送机制完成调用