什么是分类
把一个类拆分成多个模块,如需要把Person
类分成A
和B
模块,则分为Person+A
和Person+B
模块,这就用到了分类,分类可以拓展类的属性、方法、协议等信息
如图:
分类的底层结构
在objc-4
的源码中,我们搜索category_t {
可以看到:
category_t
就是一个分类的结构体,而我们所创建的的一个分类其实就是一个category_t
的结构体,category_t
里面的结构跟类对象的结构很相似,包含了name
(名称,类名),instanceMethods
(对象方法)、classMethods
(类方法)、protocols
(协议)、属性等。
在编译的时候,分类的属性、方法、协议等会先存储在这个结构体里面,在运行的时候,使用runtime
动态的把分类里面的方法、属性、协议等添加到类对象(元类对象)中,具体源码我们可以查看:
ECCAE7AB-BD89-49AE-A9BB-1AD80EDDCD99.png
最终可以找到这个方法
attachCategories
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
// 判断是否元类
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
/* 方法数组[
[method_t , method_t],
[method_t .....]
]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
/* 属性数组[
[property_t , property_t],
[property_t .....]
]
*/
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
/* 协议数组[
[peotocol_t , peotocol_t],
[peotocol_t .....]
]
*/
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[I];
// 将所有分类的对象方法,附加到类对象列表中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 将所有分类的属性,附加到类属性列表中
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// 将所有分类的协议,附加到类协议列表中
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
这里,取出所有分类的方法、属性、协议,并将他们各自添加到一个二维数组里,最后再通过attachLists
将他们添加到类对象中。
Category和Class Extension 的区别
Class Extension :
@interface Person ()
@property (nonatomic,assign)int sex;
- (void)isBig;
@end
将属性、方法等封装在 .m
文件里面,类似private
(私有)的应用
这两者的区别:
Class Extension
在编译的时候,数据就已经包含类信息里了
Category
是在运行时,通过runtime
将数据合并到类信息中
+ Load 和 +initialize 方法的区别
Load
:在runtime
加载类、分类的时候根据函数地址直接调用,程序初始化就会调用,在Category
中,先调用类的load
(根据编译顺序),再调用分类的load
(根据编译顺序)
initialize
:在类
第一次接收到消息时调用,给类发送消息(objc_msgSend)才会调用,优先调用父类的initialize
,再调用子类的initialize
,且只会调用一次(父类的initialize
可能会调用多次)
objc_msgSend() 方法实现
在objc4
源码中搜索objc_msgSend(
发现这个方法是由汇编实现的
但是我们可以大概猜出他的实现思路:
一、由于
initialize
是第一次接受到消息调用,所以initialize
的调用是在objc_msgSend
方法里,所以他的调用顺序应该是在最前面,而且是只调用一次的判断二、通过
isa
寻找类/元类对象,寻找方法调用三、如果
isa
没有寻找到对应的方法,则通过superClass
寻找父类是否有这个方法,调用
网友评论