Category的实现原理
Category编译之后的底层结构是struct category_t
,里面存储着分类的对象方法,类方法,属性,协议信息.在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类对象中)
通过runtime动态将分类中的对象方法合并到 class
的方法列表中,将类方法合并到meta-class
的方法列表中.将LDPerson
的Test
和Eat
分类转换成编译之后的64位代码.
@interface LDPerson (Test)<NSCopying>
@property (nonatomic,strong) NSString * test;
- (void)test;//.m文件中有test的实现
@end
@interface LDPerson (Eat)
@property (nonatomic,assign) int age;
- (void)eat;/.m文件中有eat方法的实现
@end
指令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LDperson+Eat.m
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
定义在objc-runtime-new.h中Category的最新结构
image.png通过终端指令,编译后的
Category
的结构:
struct _category_t {
const char *name;//类名: LDPerson
struct _class_t *cls;
const struct _method_list_t *instance_methods;//对象方法
const struct _method_list_t *class_methods;//类方法
const struct _protocol_list_t *protocols;//协议Category中可以遵守协议
const struct _prop_list_t *properties;//属性,Category中可以添加属性,不能添加成员变量
}
//将如下结构体赋值给上面的`Cagatory`结构体
static struct _category_t _OBJC_$_CATEGORY_LDPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LDPerson",//类名
0, // &OBJC_CLASS_$_LDPerson,//cls
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LDPerson_$_Test,//对象方法
0,//类方法,因为该类中没有类方法,所以类方法为空
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_LDPerson_$_Test,//协议
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LDPerson_$_Test,//属性
};
由上可知定义的LDPerson
结构体在编译完成之后,分类里面的属性,方法,协议,等信息都会存放到struct _category_t
类型的结构体变量中,然后定义一个静态的tatic struct _category_t
类型的变量,将这个变量里的值赋值给struct _category_t
类型的结构体.编译之后并没有直接合并到LDPerson
中.而是在运行时动态添加到LDPerson
中.
Category的加载处理过程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大数组中. 后面参与编译的Category数据,会在数组的前面.编译顺序:
Xcode项目 --> target --> Build Phases ---> Compile Sources
Category和Class Extension的区别
Class Extension:如下是Extension类扩展(不太合适的叫法:匿名分类)
@interface LDPerson ()
@property (nonatomic,strong) NSString * personName;
- (void)personMethod;
@end
Class Extension类扩展是在编译时将所有信息存放到该类中.而Category是在运行时将所有信息整合到类中.
Category中的Load方法什么时候调用,能继承吗?
load.initialize方法的区别是什么?
1.调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
- 调用时刻
- load是runtime加载类,分类的时候调用(只调用一次)
- initialize是类第一次接受到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)
load.initialize方法在Category中的调用顺序以及出现继承时他们之间的调用过程?
load
1.先调用load
- 先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
2.在调用分类的load
- 先编译的分类,优先调用load
initialize - 先初始化父类
- 再初始化子类(可能最终调用的是父类的initialize)
探究load方法加载顺序
代码实现如下图:
1.并没有创建LDPerson
和LDStudent
对象.即没有使用也会加载.运行过程中会加载所有的类信息.
2.每个类和分类都重写+load
方法,并打印响应的类名和方法名
3.LDStudent
继承自LDPerson
编译顺序:
image.png
通过上面两幅图分析
load
方法加载顺序:编译顺序为:
LDStudent
-->LDPerson
--> LDPerson + Test2
--> LDStudent + Test2
--> LDPerson + Test1
--> LDStudent + Test1
首先编译
LDStudent
会调用LDStudent
的laod
方法,但是会优先调用父类LDPerson
的laod
方法.所以顺序为:
LDPerson
--> LDStudent
分类的
load
方法加载顺序即为编译顺序:所以顺序为:
LDPerson + Test2
--> LDStudent + Test2
-->LDPerson + Test1
--> LDStudent + Test1
整个load方法的调用顺序即为:
LDPerson
--> LDStudent
-->LDPerson + Test2
--> LDStudent + Test2
-->LDPerson + Test1
--> LDStudent + Test1
符合上图打印结果.
接下来我们调整文件编译顺序再次推断:
image.png
首先调用类的load方法,先编译的先调用.即
AppDelegate
--> ViewController
然后是 main
函数没有load
方法,不在我们的讨论范围内.接下来是LDPerson(Test2)
-->`LDStudent(Test2).因为会优先加载类信息,然后再加载分类信息,所以会将所有类的信息加载完成,才会加载分类信息.所以:
一. 我们第一次首先应该在编译顺序中找出所有的类:
AppDelegate
-->ViewController
-->LDStudent
-->LDPerson
因为LDStudent存在继承结构,会优先调用父类的
load方法,所以顺序为:
AppDelegate-->
ViewController>
LDPerson--->
LDStudent二.找出所有的分类,分类的加载顺序即为编译顺序:
LDPerson(Test2)-->
LDStudent(Test1)-->
LDStudent(Test2)-->
LDPerson(Test1)`
所以推断结果为:
AppDelegate
ViewController
LDPerson
LDStudent
LDPerson(Test2)
LDStudent(Test1)
LDStudent(Test2)
LDPerson(Test1)
我们可以看到打印结果即为我们的推断结果.
从上面的过程我们还可以看出:
1.程序在编译阶段就会调用类和分类的
load
方法,而非在程序启动之后或用到该类(创建该类对象)的时候2.编译 -->runtime整合类,分类信息,整合所有信息之后 ---> 加载进内存 ---> APP启动
补充:
在程序运行过程中,分类信息会通过runtime动态加载到类信息当中,完成类和分类信息的整合.
+initialize方法
+initialize方法会在类第一次接收到消息时调用
调用顺序
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)
+initialize和+load区别
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点:
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用
网友评论