一、概述
Category,也就是分类,能够在不修改原类代码的基础上,为类增加一些方法和属性。
Category的好处:
- category可以在原类的基础上扩展类的方法;可以实现私有类的方法扩展以及一些源码类的扩展。也就是说不用修改原类的代码结构,比继承更为轻量级;
- category可以将类的实现分散到多个文件。减少类的冗余;
- category可以创建私有方法的前向引用:在类别里提供一个类的私有方法的声明(不需要提供实现),当实例调用的时候,编译器不会报错,运行时会调用类的私有方法,继承不能继承私有方法的实现。
- category可以向类添加非正式协议:因为Object-C里面所有类都是NSObject的子类,所以NSObject的类别里定义的函数,所有对象都能使用。
注意:
- 1.category里面不能添加成员变量。但是可以通过关联对象间接的实现成员变量效果。这种方式在关联对象学习。会讲到。
- 2.category定义的方法如果和类里面的方法同名,则会覆盖原来类定义的方法。
- 3.如果一个类有多个category,每个category定义了同一个方法,则类调用的方法是按照类的编译顺序调用,即先调用最后编译的分类的方法。
二、category分类的原理
- category编译之后的底层原理是
struct category_t
,里面存储着分类的对象方法、类方法、属性信息、协议信息。 - 在程序运行的时候,runtime会动态的将分类的数据合并到类对象中。
在验证这个问题之前,我们先分析下分类中生成的底层结构。我们先看一段代码
@interface Person (Test)
- (void)test;
@end
@implementation Person (Test)
- (void)test{
NSLog(@"%s",__func__);
}
@end
反编译成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;
};
在这个结构体内,const char *name;
是分类名;
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;
是属性列表。
在C++文件中,在struct _category_t后面有这么一段代码
static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
0,
0,
};
这段代码是按照顺序给struct _category_t
结构体内赋值。
其中_CATEGORY_INSTANCE_METHODS_Person_
方法是test实例方法:如下
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_Test_test}}
};
_CATEGORY_CLASS_METHODS_Person_
方法是test2类方法,如下:
_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_Person_Test_test2}}
};
接下来看下category方法合并的原理
runtime的源码可以直接在苹果官网上下载,这里看的是最新版objc4-723,按照
objc-os.mm
_objc_init
map_images
map_images_nolock
_read_images
轨迹,在objc-runtime-new.mm
先找到_read_images
函数:
这里只贴出加载category的源代码(不必细看)
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
...
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
... }
上面代码提到了比较重要的函数:
remethodizeClass(cls)
remethodizeClass
方法的实现:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
attachCategories方法的实现
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_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
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);
}
remethodizeClass
函数调用attachCategories(cls, cats, true )
方法,并给cls传递参数(类或者元类对象),给cats传递参数(分类列表)。cats分类列表中,包括类对象所有的分类。
方法数组
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
属性数组
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
协议数组
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
在while (i--)
循环中
- 由
auto& entry = cats->list[i]
方法取出分类列表中的某个分类; - 由
entry.cat->methodsForMeta(isMeta)
方法取出分类中的方法列表放到二维数组method_list_t
中; - 由
entry.cat->propertiesForMeta(isMeta, entry.hi)
方法取出分类中的属性列表放到二维数组property_list_t
中; - 由
entry.cat->protocols
方法取出分类中的协议信息列表,放到protocol_list_t
二维数组中。
经过循环,就把所有category里的方法,属性,协议列表都添加相应的二维数组中。
objc_class结构 -
rw->methods.attachLists(mlists, mcount);
是将所有分类的对象方法,附加到类对象的方法列表中; -
rw->properties.attachLists(proplists, propcount);
是将所有分类的属性,附加到类对象的属性列表中 -
rw->protocols.attachLists(protolists, protocount);
是将所有分类的协议,附加到类对象的协议列表中。
经过这些方法的调用,把所有分类中的信息合并到objc_class结构中。
合并过程
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;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
......
}
-
memmove
先会扩容,扩容的大小为newCount = oldCount + addedCount
。然后把原来的内存单元中的内容,往后移动。移动的距离为addedCount
数量。 -
memcpy
把分类中的方法,属性或者协议,移动到上个步骤中的空置单元格。
因为移动后,分类中的方法或者属性或者协议在内存的最前面,所有在类对象调用方法时,先调用分类中的方法或者属性。
三、Category中的load方法
-
+load
方法会在runtime加载类、分类时调用,根据函数地址直接调用。 - 每个类、分类中的
+load
方法,在程序运行过程中只会调用一次。
调用顺序
- 1、先调用类的
+load
按照编译顺序调用(先编译,先调用)
调用子类的+load
方法之前,会先调用父类的+load
- 2、在调用分类的
+load
按照编译顺序调用(先编译,先调用)
void call_load_methods(void)
{
...
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
...
}
在苹果给的源码中可以看出,call_class_loads();
先是调用类的+load
;
call_category_loads()
再调用分类的+load
。
static void call_class_loads(void)
{
...
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
...
}
在上面苹果给的源码中可以看出,load_method_t load_method = (load_method_t)classes[i].method;
经过循环调用,找到classes存储的类的地址,直接调用+load
方法。分类方法调用+load,也是如此。
四、Category中的initialize方法
-
+initialize
方法会在类第一次接收到消息时调用;是通过objc
调用顺序 - 先调用父类的
+initialize
,再调用子类的+initialize
。
如果子类没有实现+initialize
,则再次调用父类的+initialize
。即父类的+initialize
可能调用多次。
在苹果给的源码中,根据以下过程
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
来分析
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
...
}
上述方法中_class_initialize()
传入相应的类,然后调用
void _class_initialize(Class cls)
{
...
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
}
...
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
在上述方法中,调用callInitialize(cls);
并传入类
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
- 上述方法是一个递归函数,如果父类的
+initialize
没有初始化,就先调用父类的objc_msgSend
方法,如果父类有分类,并且分类中实现了+initialize
,就调用分类的+initialize
; - 如果父类的
+initialize
方法已经调用,就调用子类的+initialize
,如果子类有分类,并且分类中实现了+initialize
,就调用分类的+initialize
;
网友评论