本文將對category的源碼進行比較全面的整理分析,最后結合一些面試題進行總結锥腻,希望對讀者有所裨益嗦董。
GitHub Repo:iOSDeepAnalyse
Follow: MisterBooo · GitHub
Source: Category:從底層原理研究到面試題分析
目錄
- 1.Category源碼分析
- 2.load源碼分析
- 3.initialize源碼分析
- 4.load與initialize對比
- 5.面試題分析
源碼分析
1.源碼閱讀前的準備
本節(jié)代碼基于以下的代碼進行編譯研究:
@interface Person : NSObject
- (void)instanceRun;
+ (void)methodRun;
@property(nonatomic, copy) NSString *name;
@end
@interface Person (Eat)
@property(nonatomic, assign) int age;
- (void)instanceEat;
+ (void)methodEat;
@end
@interface Person (Drink)
- (void)instanceEat;
@property(nonatomic, copy) NSString *waters;
@end
2.objc4中的源碼
通過objc4中的源碼進行分析,可以在objc-runtime-new.h
中找到Category
的結構如下
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
不難發(fā)現(xiàn)在這個結構體重存儲著對象方法瘦黑、類方法京革、協(xié)議和屬性。接下來我們來驗證一下我們剛剛自己編寫的Person+Eat.m
這個分類在編譯時是否是這種結構供璧。
通過
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Eat.m
命令將Person+Eat.m
文件編譯成cpp
文件存崖,以下的源碼分析基于Person+Eat.cpp
里面的代碼。下面讓我們開始窺探Category的底層結構吧~
2.Person+Eat.cpp源碼
將Person+Eat.cpp
的代碼滑到底部部分睡毒,可以看見一個名為_category_t
的結構體,這就是Category的底層結構
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;// 協(xié)議列表
const struct _prop_list_t *properties;// 屬性列表
};
Person+Eat.m
這個分類的結構也是符合_category_t
這種形式的
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, // 對象方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,// 類方法列表
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat, // 協(xié)議列表
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat, // 屬性列表
};
我們開始來分析上面這個結構體的內部成員冗栗,其中Person
表示類名
對象方法列表
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat
是對象方法列表演顾,在Person+Eat.cpp
文件中可以找到_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat
具體描述
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"instanceEat", "v16@0:8", (void *)_I_Person_Eat_instanceEat}}
};
instanceEat
就是我們上述實現(xiàn)的Person+Eat
分類里面的實例方法。
類方法列表
_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat
是類方法列表蔫慧,在Person+Eat.cpp
中具體描述如下
_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"classEat", "v16@0:8", (void *)_C_Person_Eat_classEat}}
};
協(xié)議列表
_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat
是協(xié)議列表湘捎,在Person+Eat.cpp
中具體描述如下
_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying,
&_OBJC_PROTOCOL_NSCoding
};
屬性列表
_OBJC_$_PROP_LIST_Person_$_Eat
是屬性列表盐股,在Person+Eat.cpp
中具體描述如下
_OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"weight","Ti,N"},
{"height","Ti,N"}}
};
3.Category的加載處理過程
通過上面的分析,我們驗證了編寫一個分類的時候棉钧,在編譯期間,這個分類內部的確會有category_t
這種數(shù)據(jù)結構涕蚤,那么這種數(shù)據(jù)結構是如何作用到這個類的呢宪卿?分類的方法和類的方法調用的邏輯是怎么樣的呢的诵?我們接下來回到objc4源碼中進一步分析Category
的加載處理過程來揭曉Category
的神秘面紗。
我們按照如下函數(shù)的調用順序佑钾,一步一步的研究Category
的加載處理過程
void _objc_init(void);
└── void map_images(...);
└── void map_images_nolock(...);
└── void _read_images(...);
└── void _read_images(...);
└── static void remethodizeClass(Class cls);
└──attachCategories(Class cls, category_list *cats, bool flush_caches);
文件名 | 方法 |
---|---|
objc-os.mm | _objc_init |
objc-os.mm | map_images |
objc-os.mm | map_images_nolock |
objc-runtime-new.mm | _read_images |
objc-runtime-new.mm | remethodizeClass |
objc-runtime-new.mm | attachCategories |
objc-runtime-new.mm | attachLists |
在iOS 程序 main 函數(shù)之前發(fā)生了什么
中提到西疤,_objc_init
這個函數(shù)是runtime的初始化函數(shù),那我們從_objc_init
這個函數(shù)開始進行分析休溶。
/***********************************************************************
* _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
函數(shù)中找到_read_images
函數(shù),在_read_images
函數(shù)中我們找到與分類相關的代碼
// 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) {
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
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);
}
}
}
}
在上面的代碼中兽掰,主要做了以下的事情
- 1.獲取
category
列表list
- 2.遍歷
category list
中的每一個category
- 3.獲取
category
的cls
芭碍,如果沒有cls
,則跳過(continue
)這個繼續(xù)獲取下一個 - 4.如果
cat
有實例方法孽尽、協(xié)議豁跑、屬性,則調用addUnattachedCategoryForClass
泻云,同時如果cls
有實現(xiàn)的話艇拍,就進一步調用remethodizeClass
方法 - 5.如果
cat
有類方法、協(xié)議宠纯,則調用addUnattachedCategoryForClass
卸夕,同時如果cls
的元類有實現(xiàn)的話,就進一步調用remethodizeClass
方法
其中4
,5
兩步的區(qū)別主要是cls
是類對象還是元類對象的區(qū)別婆瓜,我們接下來主要是看在第4
步中的addUnattachedCategoryForClass
和remethodizeClass
方法快集。
addUnattachedCategoryForClass
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
runtimeLock.assertWriting();
//全局對象
static NXMapTable *category_map = nil;
if (category_map) return category_map;
// fixme initial map size
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
對上面的代碼進行解讀:
- 1.通過
unattachedCategories()
函數(shù)生成一個全局對象cats
- 2.我們從這個單例對象中查找
cls
,獲取一個category_list
*list
列表 - 3.要是沒有
list
指針廉白。那么我們就生成一個category_list
空間个初。 - 4.要是有
list
指針,那么就在該指針的基礎上再分配出category_list
大小的空間 - 5.在這新分配好的空間猴蹂,將這個
cat
和catHeader
寫入院溺。 - 6.將數(shù)據(jù)插入到
cats
中,key
是cls
, 值是list
remethodizeClass
static void remethodizeClass(Class cls)
{
//分類數(shù)組
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);
}
}
在remethodizeClass
函數(shù)中將通過attachCategories
函數(shù)我們的分類信息附加到該類中。
attachCategories
//cls = [Person class]
//cats = [category_t(Eat),category_t(Drink)]
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 重新分配內存
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);
}
對上面的代碼進行解讀(假設cls
是類對象磅轻,元類對象分析同理):
1.根據(jù)方法列表珍逸、屬性列表、協(xié)議列表分配內存
-
2.
cats
是這種數(shù)據(jù)結構:[category_t(Eat),category_t(Drink)聋溜,谆膳。。撮躁。]
漱病,遍歷cats
,然后- 1.獲取一個分類里面的所有對象方法,存儲在
mlist
數(shù)組中杨帽,然后再將mlist
數(shù)組添加到二維數(shù)組mlists
中 - 2.獲取一個分類里面的所有協(xié)議漓穿,存儲在
proplist
數(shù)組中,然后再將proplist
數(shù)組添加到二維數(shù)組proplists
中 - 3.獲取一個分類里面的所有屬性睦尽,存儲在
protolist
數(shù)組中器净,然后再將protolist
數(shù)組添加到二維數(shù)組protolists
中
- 1.獲取一個分類里面的所有對象方法,存儲在
3.獲取
cls
的的bits
指針class_rw_t
,通過attachLists
方法,將mlists
附加到類對象方法列表中当凡,將proplists
附加到類對象的屬性列表中山害,將protolists
附加到類對象的協(xié)議列表中
其中mlists
的數(shù)據(jù)結構如下,proplists
與protolists
同理:
[
[method_t,method_t],
[method_t,method_t]
]
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]));
}
}
在attachLists
方法主要關注兩個變量array()->lists
和addedLists
- array()->lists: 類對象原來的方法列表,屬性列表沿量,協(xié)議列表浪慌,比如Person中的那些方法等
- addedLists:傳入所有分類的方法列表,屬性列表朴则,協(xié)議列表权纤,比如Person(Eat)、Person(Drink)中的那些方法等乌妒。
上面代碼的作用就是通過memmove
將原來的類找那個的方法汹想、屬性、協(xié)議列表分別進行后移撤蚊,然后通過memcpy
將傳入的方法古掏、屬性、協(xié)議列表填充到開始的位置侦啸。
我們來總結一下這個過程:
通過Runtime加載某個類的所有Category數(shù)據(jù)
把所有Category的方法槽唾、屬性、協(xié)議數(shù)據(jù)光涂,合并到一個大數(shù)組中庞萍,后面參與編譯的Category數(shù)據(jù),會在數(shù)組的前面
將合并后的分類數(shù)據(jù)(方法忘闻、屬性钝计、協(xié)議),插入到類原來數(shù)據(jù)的前面
我們可以用如下的動畫來表示一下這個過程
- 1)服赎、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法葵蒂,也就是說如果category和原來類都有methodA,那么category附加完成之后重虑,類的方法列表里會有兩個methodA.
- 2)、category的方法被放到了新方法列表的前面秦士,而原來類的方法被放到了新方法列表的后面缺厉,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法提针,就會罷休_命爬,殊不知后面可能還有一樣名字的方法。
我們通過代碼來驗證一下上面兩個注意點是否正確
load與initialize
load方法與initialize方法的調用與一般普通方法的調用有所區(qū)別辐脖,因此筆者將其放在這一節(jié)一并分析進行想對比
load源碼分析
同樣的饲宛,我們按照如下函數(shù)的調用順序,一步一步的研究load
的加載處理過程
void _objc_init(void);
└── void load_images(...);
└── void call_load_methods(...);
└── void call_class_loads(...);
我們直接從load_images
方法進行分析
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在load_images
方法中主要關注prepare_load_methods
方法與call_load_methods
方法
prepare_load_methods
void prepare_load_methods(header_info *hi)
{
size_t count, i;
rwlock_assert_writing(&runtimeLock);
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// 確保父類優(yōu)先的順序
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
顧名思義嗜价,這個函數(shù)的作用就是提前準備好滿足 +load 方法調用條件的類和分類艇抠,以供接下來的調用。
然后在這個類中調用了schedule_class_load(Class cls)
方法久锥,并且在入?yún)r對父類遞歸的調用了家淤,確保父類優(yōu)先的順序。
call_load_methods
經(jīng)過prepare_load_methods
的準備瑟由,接下來call_load_methods
就開始大顯身手了絮重。
void call_load_methods(void)
{
static BOOL loading = NO;
BOOL more_categories;
recursive_mutex_assert_locked(&loadMethodLock);
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
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);
objc_autoreleasePoolPop(pool);
loading = NO;
}
在call_load_methods
中我們看do
循環(huán)這個方法,它調用上一步準備好的類和分類中的 +load 方法歹苦,并且確保類優(yōu)先于分類的順序青伤。
call_class_loads
call_class_loads
是load
方法調用的核心方法
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
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);
}
// Destroy the detached list.
if (classes) _free_internal(classes);
}
這個函數(shù)的作用就是真正負責調用類的 +load
方法了。它從全局變量 loadable_classes
中取出所有可供調用的類殴瘦,并進行清零操作狠角。
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
其中 loadable_classes
指向用于保存類信息的內存的首地址,loadable_classes_allocated
標識已分配的內存空間大小痴施,loadable_classes_used
則標識已使用的內存空間大小擎厢。
然后,循環(huán)調用所有類的 +load
方法辣吃。注意动遭,這里是(調用分類的 +load
方法也是如此)直接使用函數(shù)內存地址的方式 (*load_method)(cls, SEL_load)
; 對 +load
方法進行調用的,而不是使用發(fā)送消息 objc_msgSend
的方式神得。
但是如果我們寫[Student load]
時厘惦,這是使用發(fā)送消息 objc_msgSend
的方式。
舉個??:
@interface Person : NSObject
@end
@implementation Person
+ (void)load{
NSLog(@"%s",__func__);
}
@end
@interface Student : Person
@end
@implementation Student
//+ (void)load{
// NSLog(@"%s",__func__);
//}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student load];
}
return 0;
}
輸出如下:
第一句走的是load
的加載方式哩簿,而第二句走的是objc_msgSend
中消息發(fā)送機制宵蕉,isa
指針通過superclass
在父類中找到類方法。
小總結:
-
+load
方法會在runtime加載類节榜、分類時調用 - 每個類羡玛、分類的+load,在程序運行過程中只調用一次
- 調用順序
1.先調用類的+load
按照編譯先后順序調用(先編譯宗苍,先調用)
調用子類的+load之前會先調用父類的+load
2.再調用分類的+load
按照編譯先后順序調用(先編譯稼稿,先調用)
initialize源碼分析
同樣的薄榛,我們按照如下函數(shù)的調用順序,一步一步的研究initialize
的加載處理過程
Method class_getInstanceMethod(Class cls, SEL sel);
└── IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
└── IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
└── void _class_initialize(Class cls);
└── void callInitialize(Class cls);
我們直接打開objc-runtime-new.mm
文件來研究lookUpImpOrForward
這個方法
lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
rwlock_unlock_write(&runtimeLock);
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// 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
}
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
...
}
initialize && !cls->isInitialized()
判斷代碼表明當一個類需要初始化卻沒有初始化時让歼,會調用_class_initialize
進行初始化敞恋。
_class_initialize
void _class_initialize(Class cls)
{
...
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);
}
...
callInitialize(cls);
...
}
同樣的supercls && !supercls->isInitialized()
表明對入?yún)⒌母割愡M行了遞歸調用,以確保父類優(yōu)先于子類初始化谋右。
callInitialize
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
最后在callInitialize
中通過發(fā)送消息 objc_msgSend
的方式對 +initialize
方法進行調用,也就是說+ initialize
與一般普通方法的調用處理是一樣的硬猫。
舉個??:
@interface Person : NSObject
@end
@implementation Person
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Person (Eat)
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@interface Student : Person
@end
@implementation Student
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@interface Teacher : Person
@end
@implementation Teacher
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student alloc];
[Student initialize];
[Person alloc];
[Person alloc];
[Person alloc];
[Person alloc];
[Person alloc];
[Person alloc];
NSLog(@"****分割線***");
[Teacher alloc];
[Teacher initialize];
}
return 0;
}
輸出如下:
小總結:
-
+initialize
方法會在類第一次接收到消息時調用 - 調用順序
- 先調用父類的+initialize,再調用子類的+initialize
- 先初始化父類改执,再初始化子類啸蜜,每個類只會初始化1次
load與initialize對比
條件 | +load | +initialize |
---|---|---|
關鍵方法 | (*load_method)(cls, SEL_load) |
objc_msgSend |
調用時機 | 被添加到 runtime 時 | 收到第一條消息前,可能永遠不調用 |
調用順序 | 父類->子類->分類 | 父類->子類 |
調用次數(shù) | 1次 | 多次 |
是否需要顯式調用父類實現(xiàn) | 否 | 否 |
是否沿用父類的實現(xiàn) | 否 | 是 |
分類中的實現(xiàn) | 類和分類都執(zhí)行 | 覆蓋類中的方法天梧,只執(zhí)行分類的實現(xiàn) |
面試題
1.Category的使用場合是什么盔性?
- 給現(xiàn)有的類添加方法
- 將一個類的實現(xiàn)拆分成多個獨立的源文件
- 聲明私有的方法
2.Category和Class Extension的區(qū)別是什么?
- Class Extension是編譯時決議呢岗,在編譯的時候冕香,它的數(shù)據(jù)就已經(jīng)包含在類信息中
- Category是運行時決議,在運行時后豫,才會將數(shù)據(jù)合并到類信息中(可通過上面的動畫進行理解
^_^
)
- Category是運行時決議,在運行時后豫,才會將數(shù)據(jù)合并到類信息中(可通過上面的動畫進行理解
3.Category的實現(xiàn)原理悉尾?
- Category編譯之后的底層結構是
struct category_t
,里面存儲著分類的對象方法挫酿、類方法构眯、屬性、協(xié)議信息
- Category編譯之后的底層結構是
- 在程序運行的時候早龟,runtime會將Category的數(shù)據(jù)惫霸,合并到類信息中(類對象、元類對象中)(依舊可通過上面的動畫進行理解
-_-||
))
- 在程序運行的時候早龟,runtime會將Category的數(shù)據(jù)惫霸,合并到類信息中(類對象、元類對象中)(依舊可通過上面的動畫進行理解
4.一個類的有多個分類方法葱弟,分類中都含有與原類同名的方法壹店,請問調用改方法時會調用誰的方法?分類會覆蓋原類的方法嗎芝加?
不會覆蓋硅卢!所有分類的方法會在運行時將它們的方法都合并到一個大數(shù)組中,后面參與編譯的Category數(shù)據(jù)藏杖,會在數(shù)組的前面将塑,然后再將該數(shù)組合并到類信息中,調用時順著方法列表的順序查找蝌麸。
5.load與initialize的區(qū)別
見load
與initialize
對比章節(jié)的表格
6.Category能否添加成員變量点寥?如果可以,如何給Category添加成員變量来吩?
不能直接給Category添加成員變量开财,但是可以通過關聯(lián)對象或者全局字典等方式間接實現(xiàn)Category有成員變量的效果