- 什么是類別(
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
調用;