iOS原理篇(三): 关于Category
- 什么是类别(
Category
) Category
的使用场合Category
实现原理+load()
和+initialize()
- 总结
一、什么是类别
类别(
Category
)是Objective-C
中一个灵活的类扩展机制,用于在不获悉、不改变原来代码的情况下往一个已经存在的类中添加新的方法,Category
扩展的新方法有更高的优先级,会覆盖类中同名的已有方法。类别的设计体现了面向对象的核心原则,即开放封闭原则(Open Closed Principle
,OCP
)。对扩展开放,对修改封闭,从而降低代码的耦合度。
二、Category
的使用场合
- 将类的实现分散到多个不同文件或多个不同框架中(为已有的类扩充新的方法)
- 创建对私有方法的前向引用
- 可以向对象添加非正式协议
类别(Category
)和 类扩展(Extension
)的区别:
- 类扩展可以添加属性,类扩展添加的方法是必须要实现的,类扩展可以认为是一个私有的匿名类别,因为类扩展定义在
.m
文件头部,添加的属性和方法都没有暴露在头文件,所以在不考虑运行时特性的前提下,这些扩展属性和方法只能在类内部使用,一定程度上可以说是实现了私有的机制; - 类扩展(
Class Extension
)在编译的时候,它的数据就已经包含在类信息中,而类别(Category
)是在运行时,才会将数据合并到类信息中;
三、Category
实现原理
Category
编译之后的底层结构是struct category_t
,里面存储着分类的对象方法、类方法、属性、协议信息,在程序运行的时候,runtime
会将Category
的数据,合并到类信息中(类对象、元类对象中);
下面代码为DJTPerson
添加了两个分类,之后分析都基于这段代码
//DJTPerson.h
@interface DJTPerson : NSObject
{
int _age;
}
- (void)run;
@end
//DJTPerson.m
@implementation DJTPerson
- (void)run
{
NSLog(@"run");
}
@end
//分类1
//DJTPerson+Test.h
@interface DJTPerson (Test)<NSCoding>
@property (assign, nonatomic) int age;
- (void)test;
+ (void)abc;
-(void)setAge:(int)age;
-(int)age;
@end
//DJTPerson+Test.m
@implementation DJTPerson (Test)
- (void)test
{
NSLog(@"test");
}
+ (void)abc
{
NSLog(@"abc");
}
- (void)setAge:(int)age
{
}
- (int)age
{
return 10;
}
@end
//分类2
//DJTPerson+Test2.h
@interface DJTPerson (Test2)
@end
//DJTPerson+Test2.m"
@implementation DJTPerson (Test2)
- (void)run
{
NSLog(@"DJTPerson (Test2) - run");
}
@end
我们知道实例对象的 isa
指针指向类对象,类对象的isa
指针指向元类对象,当person
对象调用run
方法时,通过isa
指针找到类对象,然后在类对象的对象方法列表中查找方法,如果没找到就通过类对象的superclass
指针找到父类对象,接着寻找run
方法。
那么调用分类的方法时,是否按照同样的步骤呢?其实,OC
中处理分类需要两步:
- 分类经过编译,会生成
_category_t
这样的一个结构体,分类中的数据都会存储在这个结构体中,多少个分类对应对应多少个这样的结构体; - 编译完以后,通过
runtime
动态将生成的结构体中存放的分类数据合并到类对象、元类对象中。
为了验证以上描述,首先通过clang
编译器将分类转换为C++
,查看分类编译后结构:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc DJTPerson+Test.m
在生成的C++
文件中,会生成一个_category_t
的结构体
struct _category_t {
const char *name;//所属类名,本文为DJTPerson
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;// 属性
};
在_category_t
结构体中,存放着类名name
、对象方法列表instanceMethods
、类方法列表classMethods
、协议列表protocols
和属性列表classProperties
;并没有存放成员变量的列表,这也说明了分类中是不允许添加成员变量的,分类中添加的属性也不会帮助我们自动生成成员变量,只会生成get
和set
方法的声明,需要我们自己去实现;
接着,在文件中我们可以看到_method_list_t
类型的结构体:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_DJTPerson_Test_test},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_DJTPerson_Test_setAge_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_DJTPerson_Test_age}}
};
从_OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test
这个结构体名字看出它是 INSTANCE_METHODS
对象方法,并且为这个结构体赋值,结构体中存储了方法占用的内存、方法数量和方法列表。并且找到分类中我们实现的对象方法:test
、setAge
、age
;
同样的,可以看到C++
文件中还包含了_method_list_t
类型的类方法结构体:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"abc", "v16@0:8", (void *)_C_DJTPerson_Test_abc}}
};
从名字也看出_OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test
这个就是类方法列表结构体;
接下来是协议方法列表:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"encodeWithCoder:", "v24@0:8@16", 0},
{(struct objc_selector *)"initWithCoder:", "@24@0:8@16", 0}}
};
struct _protocol_t _OBJC_PROTOCOL_NSCoding __attribute__ ((used)) = {
0,
"NSCoding",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCoding
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCoding = &_OBJC_PROTOCOL_NSCoding;
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCoding
};
属性列表:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"age","Ti,N"}}
};
最后在_OBJC_$_CATEGORY_DJTPerson_$_Test
结构体中对上面的结构体一一赋值:
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_DJTPerson;
static struct _category_t _OBJC_$_CATEGORY_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"DJTPerson",
0, // &OBJC_CLASS_$_DJTPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_DJTPerson_$_Test,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_DJTPerson_$_Test,
};
static void OBJC_CATEGORY_SETUP_$_DJTPerson_$_Test(void ) {
_OBJC_$_CATEGORY_DJTPerson_$_Test.cls = &OBJC_CLASS_$_DJTPerson;
}
最后将_OBJC_$_CATEGORY_DJTPreson_$_Test
的cls
指针指向_class_t
类型的OBJC_CLASS_$_DJTPreson
结构体地址,所以cls
指向的应该是分类的主类类对象的地址
通过以上分析,分类编译后确实将我们定义的对象方法,类方法,属性等都存放在_catagory_t
结构体中,接下来分析runtime
源码,查看_catagory_t
中存储的方法、属性、协议等是如何存储在类对象中的。
首先来到runtime
初始化函数
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
在&map_images
读取模块(images
这里代表模块)返回的map_images_nolock
函数中找到_read_images
函数,在_read_images
函数中我们找到分类相关代码:
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
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);
}
}
}
}
这段代码用来查找有没有分类,通过_getObjc2CategoryList
函数获取到分类列表进行遍历,来获取其中的方法、协议和属性等,而最终都调用了remethodizeClass(cls)
函数,我们查看remethodizeClass(cls)
函数:
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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
函数接收了类对象cls
和分类数组cats
,如一开始写的代码所示,一个类可以有多个分类,之前我们说到一个分类对应一个category_t
结构体,那么多个分类则将这些这些category_t
保存在category_list
中。再看attachCategories
函数:
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
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_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);
}
从源码看出,首先根据方法列表、属性列表和协议列表malloc
分配内存,根据多少个分类以及每一块方法需要多少内存来分配相应的内存地址。遍历分类方法、属性以及协议放入对应 mlist
、proplists
、protolosts
数组中。
然后通过类对象的data()
方法,拿到类对象的class_rw_t
结构体rw
,而class_rw_t
中存放着类对象的方法、属性和协议等数据,rw
结构体通过类对象的data
方法获取,所以rw
里面存放这类对象里面的数据。
接着分别通过rw
调用方法列表、属性列表、协议列表的attachList
函数,将所有的分类的方法、属性、协议列表数组传进去,我们大致可以猜想到在attachList
方法内部将分类和本类相应的方法、属性,和协议进行了合并,我们来看一下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;
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]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
上述源代码中,array()->lists
数组代表类对象原来的方法列表、属性列表和协议列表, addedLists
传入所有分类的方法列表、属性列表和协议列表;attachLists
函数中最重要的两个方法为memmove
内存移动和memcpy
内存拷贝,memmove
能保证以前的数据能完整的挪动到指定位置;
经过memmove
和memcpy
方法之后,分类的方法、属性和协议列表被放在了类对象中原本存储的方法、属性和协议列表前面。
那为什么要将分类方法列表追加到本来的对象方法前面呢,这是为了保证分类方法优先调用,我们知道当分类重写本类的方法时,会覆盖本类的方法;其实经过上面的分析我们知道本质上并不是覆盖,而是优先调用,本类的方法依然在内存中,可以通过打印类的所有方法进行查看,再DJTPerson
类中存储着两个run
方法;
- (void)printMethodNamesOfClass:(Class)cls
{
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ - %@", cls, methodNames);
}
- (void)viewDidLoad {
[super viewDidLoad];
DJTPreson *p = [[DJTPreson alloc] init];
[p run];
[self printMethodNamesOfClass:[DJTPreson class]];
}
打印结果:
2019-05-04[49082:260754984] DJTPerson (Test2) - run
2019-05-04[49082:260754984] DJTPerson - test, run, run, setAge:, setAge:, age, age,
四、+load()
和+initialize()
+load
方法调用原理
+load
方法是在runtime
加载类、分类的时候调用,每个类、分类的+load
方法,在程序运行过程中只调用一次;他和其他方法的调用不同,不是通过消息机制(isa一层层查找),而是在类、分类加载时直接通过函数地址调用。
下面示例代码中,定义DJTPerson
类和它的两个分类Test1
、Test2
:
// DJTPerson
@interface DJTPerson : NSObject
@end
@implementation DJTPerson
+ (void)load
{
NSLog(@"DJTPerson +load");
}
@end
//分类DJTPerson+Test1
@interface DJTPerson (Test1)
@end
@implementation DJTPerson (Test1)
+ (void)load
{
NSLog(@"DJTPerson(Test1) +load");
}
@end
//分类DJTPerson+Test2
@interface DJTPerson (Test2)
@end
@implementation DJTPerson (Test2)
+ (void)load
{
NSLog(@"DJTPerson(Test2) +load");
}
@end
直接运行程序,即使没有在main
函数中引用上述头文件,依然有如下打印:
这说明虽然并没有使用到这些类,但程序运行这些类会被载进内存,就会调用它们的
+load
方法;这和前面所说的在分类中定义同名的run
方法会覆盖原类的run
方法不同,明明分类中有定义load
方法,覆盖了原类中的load
方法,那为什么DJTPerson
中的load
方法还会被调用呢?(这里说覆盖并不准确,而是分类中同名方法经合并会放在方法列表的前面);通过阅读运行时源码,在运行是入口找到
load_images
方法:load_images
方法内部会调用call_load_methods(void)
方法从源码看出,先调用类的
load
方法,在调用分类的load
方法,这和编译顺序无关,如下调换编译顺序,依然先调用 DJTPerson
的load
方法:那类的load
方法又是如何调用呢?进入call_class_loads()
方法:
这里调用类的
load
方法是直接通过函数指针进行调用,不会向之前调用run
方法那样去方法列表查找,或者说不是通过消息发送机制的形式。
分类的load
方法是如何调用呢?进入call_category_loads(void)
方法:
我们发现它也是通过指针指向分类
load
方法地址,然后直接调用;总结:从上面看出确实是优先调用类的
load
方法,然后调用分类的load
方法;
接着,考虑有多个类,一个类有多个分类,以及类存在父类的情况,我们看看此时+load
方法的调用顺序,新增DJTDog
和DJTCat
两个类继承自NSObject
,新增DJTStudent
类继承自DJTPerson
,同时为DJTStudent
添加两个分类Test1
和Test2
,编译顺序如下:
从打印结果看出:
- 一定是优先调用类的
+load
方法,然后调用分类+load
方法,这个和编译顺序无关; - 类与类之间
+load
方法调用顺序和编译顺序有关,比如DJTDog
在DJTCat
和DJTStudent
之前调用+load
方法; - 在调用子类的
+load
方法之前,会保证其父类的+load
方法已经调用,即父类的+load
方法优先调用,即使父类编译顺序在子类后面,比如DJTPerson
和DJTStudent
- 分类的
+load
方法调用顺序和编译顺序有关,先编译先调用。
+initialize
方法调用原理
+initialize
方法会在类第一次接收到消息时调用,并且它的调用顺序是先调用父类的+initialize
,再调用子类的+initialize
,前提是父类的+initialize
方法没有被调用过。
查看源码,在objc-class.mm
文件中,有一个获取对象方法的函数class_getInstanceMethod()
和一个获取类方法的函数class_getClassMethod()
,在class_getClassMethod()
函数中其实也是调用的class_getInstanceMethod()
方法,上源码:
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
内部调用lookUpImpOrNil()
,再调用lookUpImpOrForward()
:
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();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
...................
上面if
中判断传入的参数initialize
,表示是否需要初始化,cls->isInitialized()
判断这个类是否已经初始化,在满足需要初始化并且类未初始化时,调用_class_initialize()
方法进行初始化,我们看一下_class_initialize()
中都做了什么:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
.............
@try
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
在if
判断中,如果存在父类,并且父类没有初始化,先去初始化父类,即调用callInitialize(cls)
方法:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
从上面源码看出,+initialize()
方法最终是通过objc_msgSend
消息发送机制调用的,消息发送机制通过isa
指针找到对应的方法与实现,因此如果分类方法中有实现,会优先调用分类方法中的实现;
综上,我们为DJTPreson
、DJTStudent
、DJTStudent+Test
添加initialize方法,当类第一次接收到消息时(或者说第一次使用类的时),就会调用initialize
,在调用子类的initialize
之前,会先保证调用父类的initialize
方法,如果之前已经调用过initialize
,就不会再调用initialize
方法了,当分类重写initialize
方法时会先调用分类的方法。
五、总结
-
Catagory
中有load
方法吗?load
方法是什么时候调用的?load
方法能继承吗?
Category
中有load
方法,load
方法在程序启动装载类信息的时候就会调用,load
方法可以继承,但一半不会手动去调用,调用子类的load
方法之前,会先调用父类的load
方法。 -
load
、initialize
方法的区别是什么?
调用方式:load
是根据函数地址直接调用,initialize
是通过objc_msgSend
调用;
调用时刻:load
是runtime
加载类、分类的时候调用(只会调用1次),initialize
是类第一次接收到消息的时候调用,每一个类只会initialize
一次(父类的initialize
方法可能会被调用多次); -
它们在
Category
中调用的顺序是什么?以及出现继承时它们之间的调用过程?
load
方法:先编译哪个类,就先调用那个类的load
,在调用load
之前会先调用父类的load
方法;分类中load
方法不会覆盖本类的load
方法,先编译的分类优先调用load
方法;
initialize
方法:先初始化父类,之后再初始化子类;如果子类没有实现+initialize
,会调用父类的+initialize
(所以父类的+initialize
可能会被调用多次),如果分类实现了+initialize
,就覆盖类本身的+initialize
调用;