通過探索Category底層原理回答以下問題
- Category是否可以添加方法、屬性、成員變量?Category是否可以遵守Protocol?
- Category的本質(zhì)是什么,在底層是怎么存儲的?
- Category的實現(xiàn)原理是什么,Catagory中的方法是如何調(diào)用到的?
- Category中是否有Load方法,load方法是什么時候調(diào)用的?
- load、initialize的區(qū)別
在Category實現(xiàn)原理(一)中我們通過窺探Category底層結(jié)構(gòu)回答了問題1、2,下面我們繼續(xù)探究。
Category中的方法調(diào)用順序 - 表象
創(chuàng)建父類MGCPerson,為其添加分類MGCPerson+Sport和MGCPerson+Eat,創(chuàng)建子類MGCStudent,并添加分配MGCStudent+Study,然后分別添加方法- (void)life和+ (void)life
@implementation MGCPerson
- (void)life {
NSLog(@"MGCPerson : - (void)life");
}
+ (void)life {
NSLog(@"MGCPerson : + (void)life");
}
@end
@implementation MGCPerson (Sport)
- (void)life {
NSLog(@"MGCPerson (Sport) : - (void)life");
}
+ (void)life {
NSLog(@"MGCPerson (Sport) : + (void)life");
}
@end
@implementation MGCPerson (Eat)
- (void)life {
NSLog(@"MGCPerson (Eat) : - (void)life");
}
+ (void)life {
NSLog(@"MGCPerson (Eat) : + (void)life");
}
@end
@implementation MGCStudent
- (void)life {
NSLog(@"MGCStudent : - (void)life");
}
+ (void)life {
NSLog(@"MGCStudent : + (void)life");
}
@end
@implementation MGCStudent (Study)
- (void)life {
NSLog(@"MGCStudent (Study) : - (void)life");
}
+ (void)life {
NSLog(@"MGCStudent (Study) : + (void)life");
}
@end
在main函數(shù)中創(chuàng)建MGCStudent類,調(diào)用life方法,觀察打印
int main(int argc, const char * argv[]) {
@autoreleasepool {
MGCStudent *student = [[MGCStudent alloc] init];
[student life];
[MGCStudent life];
MGCPerson *person = [[MGCPerson alloc] init];
[person life];
[MGCPerson life];
}
return 0;
}
// 打印結(jié)果
MGCStudent (Study) : - (void)life
MGCStudent (Study) : + (void)life
MGCPerson (Sport) : - (void)life
MGCPerson (Sport) : + (void)life
觀察打印結(jié)果我們可以得出一個結(jié)論:Category中的方法會”覆蓋“原類中的方法;仔細(xì)觀察我們發(fā)現(xiàn)MGCPerson相關(guān)的調(diào)用打印全部來自MGCPerson (Sport)中的方法,但我們明明在MGCPerson的兩個分類MGCPerson (Sport)、MGCPerson (Eat)中都實現(xiàn)了life方法,為什么優(yōu)先調(diào)用了MGCPerson (Sport)中的life方法?我們猜測這和文件的編譯順序有關(guān),通過在Build Phase -> Compile source中調(diào)整文件順序觀察打印結(jié)果,我們發(fā)現(xiàn)后編譯的分類文件優(yōu)先調(diào)用。
總結(jié)起來就是:
- 分類中的方法實現(xiàn)會"覆蓋"原類中的方法
- 后編譯的分類文件優(yōu)先級更高
繼續(xù)研究為什么調(diào)用順序是這樣
Category方法調(diào)用順序 - 本質(zhì)
為什么去runtime中找對應(yīng)源碼?
- OC中的方法調(diào)用簡單的說就是通過實例對象(或類對象)的isa指針和類對象(或元類對象)的superClass指針去類(或元類)對象中查找方法。
- 通上篇文章的探究我們知道,Category中的方法、屬性等編譯后是存儲在
category_t結(jié)構(gòu)體中的,也就是說編譯后分類中的方法并沒有合并到類(或元類)中,我們是無法在類對象(或元類對象)中找到Category中的方法的。 - 但是最終調(diào)用的時候我們卻可以通過isa和superClass指針找到這些方法。所以我們有理由猜測runtime幫我們做了方法合并
為什么從void _objc_init(void)開始查找?
簡單的說下APP安裝到運(yùn)行的過程:
- 從appStore下載app,簽名認(rèn)證通過后裝載到手機(jī)磁盤中
- 點擊APP,系統(tǒng)內(nèi)核部署好APP(進(jìn)程)運(yùn)行的初始環(huán)境,找到對應(yīng)APP的可執(zhí)行文件(
Mach-O文件)- Mach-O是蘋果系統(tǒng)的一種文件格式,app的可執(zhí)行文件、動態(tài)庫、靜態(tài)庫最終都是這種文件格式
- 根據(jù)Mach-O文件找到dyld的路徑,內(nèi)核加載dyld,dyld從系統(tǒng)留下的原始調(diào)用棧引導(dǎo)和啟動自己
- dyld(dynamic link editor) 動態(tài)鏈接器,用于鏈接動態(tài)庫、資源、app可執(zhí)行文件格式的Mach-O文件
- dyld 本身也是一種Mach-O文件
- dyld將APP可執(zhí)行文件(Mach-O文件)加載到內(nèi)存中,跟據(jù)APP可執(zhí)行文件中的
LoadCommands中的信息去系統(tǒng)共享緩存區(qū)(dyld shared cache)將引用的動態(tài)庫(Mach-O文件)遞歸加載進(jìn)內(nèi)存 - 鏈接包括可執(zhí)行文件在內(nèi)APP所需的所有的Mach-O文件
- 初始化除可執(zhí)行文件外的所有Mach-O文件
- 初始化APP可執(zhí)行文件,調(diào)用app中的
main函數(shù),到這一步就進(jìn)入我們熟悉的APP入口main函數(shù)了
_objc_init就是runtime的初始化函數(shù),也就是在上述第6步時調(diào)用的。
在runtime源碼中搜索_objc_init
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);
}
可以看到在一系列的初始化后,調(diào)用了_dyld_objc_notify_register并且傳遞了三個函數(shù),去dyld源碼中搜索函數(shù)_dyld_objc_notify_register的定義
typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
根據(jù)注釋在_dyld_objc_notify_register中會調(diào)用傳入的mapped函數(shù),并傳入已映射進(jìn)內(nèi)存的objc image(objc對應(yīng)的模塊)
objc image中存儲著我們定義的類及分類信息,接下來去runtime源碼中查看map_images函數(shù)
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
這個函數(shù)實際返回的是map_images_nolock函數(shù)的地址,繼續(xù)查看map_images_nolock函數(shù),map_images_nolock函數(shù)中查找了所有包含Objective-C的image ,然后調(diào)用_read_images處理這些image,繼續(xù)查看_read_images函數(shù)
在_read_images函數(shù)中找到處理分類的代碼塊
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// Discover categories.
for (EACH_HEADER) { // 遍歷image
category_t **catlist =
_getObjc2CategoryList(hi, &count); // 獲取當(dāng)前image中的分類列表
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) { // 遍歷分類
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) { // 分類對應(yīng)的原類已經(jīng)釋放了,清空這個分類,打印信息信息,繼續(xù)下次循環(huán)
// 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) // 分類有方法、協(xié)議或?qū)傩? {
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);
}
}
}
}
_read_images函數(shù)讀取完image中的Category信息后存放起來,然后調(diào)用remethodizeClass函數(shù)。
remethodizeClass中又調(diào)用了附加分類的方法attachCategories,重點來了,下邊我們看下attachCategories函數(shù)。注釋也很重要,注意下述代碼中的注釋
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)); //malloc一個二維數(shù)組存放所有分類的方法列表
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists)); //malloc一個二維數(shù)組存放所有分類的屬性列表
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists)); //malloc一個二維數(shù)組存放所有分類的協(xié)議列表
// 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--) { // 注意這里,是i--也就是說是倒序處理分類的
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; // 按編譯倒序(i--)將各分類的方法列表(mlist) 按正序(mcount++) 放進(jìn) 二維方法列表(mlists)中
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;// 按編譯倒序(i--)將各分類的屬性列表(proplist) 按正序(propcount++) 放進(jìn) 二維屬性列表(proplists)中
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;// 按編譯倒序(i--)將各分類的協(xié)議列表(protolist) 按正序(protocount++) 放進(jìn) 二維協(xié)議列表(protolists)中
}
}
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);// 將某個類的所有分類的協(xié)議列表附加到類的方法列表中
free(protolists);
}
attachCategories按編譯倒序將各分類中的方法、協(xié)議、屬性列表分別整合成一個二維數(shù)組后,又各自調(diào)用了attachLists方法將整合后的分類信息附加到原類(target class)的cls->data->rw中
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))); // 數(shù)組擴(kuò)容
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0])); // 從array()->lists位置開始移動oldCount * sizeof(array()->lists[0])字節(jié)到array()->lists + addedCount位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0])); // addedLists位置來時,拷貝addedCount * sizeof(array()->lists[0])字節(jié)到array()->lists,即將拷貝到array()的前addedCount位置上
}
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]));
}
}
分析上述代碼,可以看到是先將原類中的方法后移,然后再將分類中的方法加入到原類中的,到此我們可以得出以下結(jié)論
- 分類中的方法優(yōu)先級高于原類中的方法
- 后編譯的分類優(yōu)先級高于先編譯的分類
- 我們常說的分類方法覆蓋原類方法并不是真正的覆蓋,只是objc_msgSend在分類中找到方法實現(xiàn)后不再繼續(xù)查找
Category中的+load方法
同樣的我們先分別在類及其分類中實現(xiàn)+load和+ (void)initialize方法,供后續(xù)分析使用
@interface MGCPerson : NSObject
@end
@implementation MGCPerson
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation MGCPerson (Sport)
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation MGCPerson (Eat)
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@interface MGCStudent : MGCPerson
@end
@implementation MGCStudent
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation MGCStudent (Study)
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation MGCStudent (Holiday)
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@interface MGCDog : NSObject
@end
@implementation MGCDog
+ (void)load {
NSLog(@"%s", __func__);
}
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
添加好代碼后,不做任何調(diào)用和對象創(chuàng)建,直接運(yùn)行程序,打印如下
+[MGCDog load]
+[MGCPerson load]
+[MGCStudent load]
+[MGCPerson(Eat) load]
+[MGCPerson(Sport) load]
+[MGCStudent(Holiday) load]
+[MGCStudent(Study) load]
可以發(fā)現(xiàn)未做任何調(diào)用和對象創(chuàng)建的情況下,也會執(zhí)行+ (void)load方法。
嘗試在xcode->targets->build phases->compile sources中調(diào)整文件編譯順序,發(fā)現(xiàn)
- 類中的load方法始終優(yōu)先于分類調(diào)用,且不受編譯順序影響
- 父類中的load方法優(yōu)先于子類調(diào)用,且不受編譯順序影響
- 無繼承關(guān)系的類,先編譯的先調(diào)用
- 分類中的load方法先編譯的先調(diào)用,且與其對應(yīng)原類的繼承關(guān)系無關(guān)
既然我們沒有調(diào)用,我們猜測是runtime啟動時調(diào)用了load方法,繼續(xù)查看runtime的入口函數(shù)_objc_init
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);
}
從前邊對void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped);的分析我們知道第二個參數(shù)load_images函數(shù)中調(diào)用了+load方法,從這里也可以看出load_images中的load不是加載的意思,是load方法的意思
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); // 準(zhǔn)備工作
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods(); // 調(diào)用load方法
}
load_images中先調(diào)用了prepare_load_methods再進(jìn)一步調(diào)用了void call_load_methods(void)
先看下prepare_load_methods做了什么處理,注釋很重要,不要忽略它
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count); // 獲取所有類
for (i = 0; i < count; i++) { // 正序遍歷所有類(編譯順序)
schedule_class_load(remapClass(classlist[i])); // 整理類的load方法
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &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); // 正序添加到表中
}
}
分析上述代碼可知:類中的load方法優(yōu)先于Category中的load方法被規(guī)劃入表中;且類及分類各自按編譯正序規(guī)劃load方法到表中。
繼續(xù)查看schedule_class_load方法,注釋依然很重要,不要忽略它
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return; // 如果已經(jīng)處理過此類的load方法,則不再處理,所以每個load方法只會調(diào)用一次
// Ensure superclass-first ordering
schedule_class_load(cls->superclass); // 遞歸調(diào)用schedule_class_load方法,并傳入父類
add_class_to_loadable_list(cls); // 將類中的load方法加入表中
cls->setInfo(RW_LOADED); // 置為已處理
}
分析上述代碼可知:父類中的load方法優(yōu)先于子類中的load方法加入表中。
到此準(zhǔn)備工作就做完了,接下來看下調(diào)用void call_load_methods(void)
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// 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(); // 調(diào)用所有類的load方法,地都用完成后loadable_classes_used置為0
}
// 2. Call category +loads ONCE
more_categories = call_category_loads(); // 調(diào)用所有分類的load方法
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
分析上述代碼可知:調(diào)用load方法時優(yōu)先調(diào)用類中的load方法,然后再調(diào)用分類中的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++) { // 按取出結(jié)果正序調(diào)用
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);// 調(diào)用load方法
}
// Destroy the detached list.
if (classes) free(classes);
}
分析上述代碼可知:類中的load方法按表中正序調(diào)用,結(jié)合前邊load方法的入表順序可知,先編譯的類先調(diào)用,且父類優(yōu)先于子類調(diào)用。
查看call_category_loads(void)方法
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) { // 按表中正序調(diào)用
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load); // 調(diào)用load方法
cats[i].cat = nil;
}
}
分析上述代碼可知:分類中的load方法按表中正序調(diào)用,結(jié)合前邊入表順序可知,先編譯的分類先調(diào)用。
綜合以上分析,已經(jīng)驗證了本小結(jié)開始時的現(xiàn)象分析結(jié)論,這里不再贅述
Category中的+ (void)initialize方法
同樣先看現(xiàn)象,代碼和上一小結(jié)(Category中的+load方法)中一樣,這里不再列出
上一小結(jié)中我們在不添加任何方法調(diào)用、對象創(chuàng)建的情況下直接運(yùn)行程序,發(fā)現(xiàn)調(diào)用了+load方法,但是并沒有調(diào)用+ (void)initialize
嘗試添加如下代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
[MGCStudent class];
[[MGCDog alloc] init];
[[MGCDog alloc] init];
[[MGCDog alloc] init];
[[MGCDog alloc] init];
}
return 0;
}
打印結(jié)果
+[MGCPerson(Eat) initialize]
+[MGCStudent(Study) initialize]
+[MGCDog initialize]
可以看到在我們沒有調(diào)用+ (void)initialize的情況下,+ (void)initialize方法依然被調(diào)用了,且無論調(diào)用的是類類方法還是實例方法,+ (void)initialize都會被調(diào)用并且只會調(diào)用一次。
因此我們猜測是類第一次接收到消息時調(diào)用了+ (void)initialize方法。即第一次接收objc_msgSend消息時調(diào)用了+ (void)initialize方法。
嘗試去runtime中查找objc_msgSend方法,這個方法的源碼是匯編的。
最終調(diào)用到了void _class_initialize(Class cls)方法
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); // 遞歸調(diào)用_class_initialize方法,并傳入父類指針
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) { // 如果類沒有調(diào)用Initialize方法,則準(zhǔn)備調(diào)用
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
callInitialize(cls); // 調(diào)用當(dāng)前類的callInitialize方法
}
}
分析上述代碼可知:在調(diào)用自己的callInitialize之前會先調(diào)用父類的callInitialize
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
分析上述代碼可知:Initialize最終是通過objc_msgSend調(diào)用的,既然最終是通過objc_msgSend調(diào)用的,那就遵從objc_msgSend方法調(diào)用的原理。即分類方法會"覆蓋"原類的中的方法。
綜合以上可知:
- 父類中的
Initialize優(yōu)先于子類中的Initialize方法調(diào)用 - 分類中的
Initialize優(yōu)先于類中的Initialize方法調(diào)用
注意:如果子類及子類分類中都沒有實現(xiàn)Initialize方法,根據(jù)objc_msgSend方法查找順序,最終會調(diào)用父類的Initialize方法。也就是說上一小節(jié)中定義的MGCStudent對象及其分類MGCStudent (Study)和MGCStudent (Holiday)如果都沒實現(xiàn)Initialize,調(diào)用[MGCStudent class]會調(diào)用兩次Initialize方法,一次是遞歸調(diào)用時父類(MGCPerson)調(diào)用的,一次是自己調(diào)用時objc_msgSend根據(jù)方法查找順序自己(MGCStudent)調(diào)用的