Category

作者: 曹来东 | 来源:发表于2018-08-29 09:06 被阅读6次

Category的实现原理

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法,类方法,属性,协议信息.在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类对象中)
通过runtime动态将分类中的对象方法合并到 class的方法列表中,将类方法合并到meta-class的方法列表中.将LDPersonTestEat分类转换成编译之后的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的加载处理过程

  1. 通过Runtime加载某个类的所有Category数据
  2. 把所有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调用
  1. 调用时刻
  • load是runtime加载类,分类的时候调用(只调用一次)
  • initialize是类第一次接受到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)

load.initialize方法在Category中的调用顺序以及出现继承时他们之间的调用过程?

load
1.先调用load

  • 先调用类的load
  • 先编译的类,优先调用load
  • 调用子类的load之前,会先调用父类的load

2.在调用分类的load

  • 先编译的分类,优先调用load
    initialize
  • 先初始化父类
  • 再初始化子类(可能最终调用的是父类的initialize)

探究load方法加载顺序

代码实现如下图:
1.并没有创建LDPersonLDStudent对象.即没有使用也会加载.运行过程中会加载所有的类信息.
2.每个类和分类都重写+load方法,并打印响应的类名和方法名
3.LDStudent继承自LDPerson

image.png
编译顺序:
image.png
通过上面两幅图分析load方法加载顺序:
编译顺序为:
LDStudent -->LDPerson --> LDPerson + Test2--> LDStudent + Test2--> LDPerson + Test1--> LDStudent + Test1
首先编译LDStudent会调用LDStudentlaod方法,但是会优先调用父类LDPersonlaod方法.
所以顺序为: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)

image.png
我们可以看到打印结果即为我们的推断结果.
从上面的过程我们还可以看出:
1.程序在编译阶段就会调用类和分类的load方法,而非在程序启动之后或用到该类(创建该类对象)的时候
2.编译 -->runtime整合类,分类信息,整合所有信息之后 ---> 加载进内存 ---> APP启动
补充:
在程序运行过程中,分类信息会通过runtime动态加载到类信息当中,完成类和分类信息的整合.

+initialize方法

+initialize方法会在类第一次接收到消息时调用
调用顺序
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)

+initialize和+load区别

+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点:
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用

相关文章

网友评论

      本文标题:Category

      本文链接:https://www.haomeiwen.com/subject/fjbfwftx.html