Objective-C之Category的底層實(shí)現(xiàn)原理
Objective-C的+initialize方法調(diào)用原理分析
別人是這么說的
-
調(diào)用時(shí)機(jī):
+load
方法會(huì)在Runtime加載類對(duì)象(class
)和分類(category
)的時(shí)候調(diào)用 -
調(diào)用頻率:每個(gè)類對(duì)象、分類的
+load
方法苟跪,在工程的整個(gè)生命周期中只調(diào)用一次 -
調(diào)用順序:
- 先調(diào)用類對(duì)象(
class
)的+load
方法:- 類對(duì)象的
load
調(diào)用順序是按照 類文件的編譯順序 進(jìn)行先后調(diào)用赫冬; - 調(diào)用子類
+load
之前會(huì)先調(diào)用父類的+load
方法
- 類對(duì)象的
- 再調(diào)用分類(
category
)的+load方法:按照編譯先后順序調(diào)用(先編譯的伐割,先被調(diào)用)
- 先調(diào)用類對(duì)象(
一鸿摇、load方法的調(diào)用時(shí)機(jī)和調(diào)用頻率
+load
方法是在程序一啟動(dòng)運(yùn)行萨惑,加載鏡像中的類對(duì)象(class
)和分類(category
)的時(shí)候就會(huì)調(diào)用剩岳,只會(huì)調(diào)用一次贞滨,不論在項(xiàng)目中有沒有用到該類對(duì)象或者該分類,他們統(tǒng)統(tǒng)都會(huì)先被加載進(jìn)內(nèi)存,因?yàn)轭惖募虞d只有一次晓铆,所以所有的load方法肯定都會(huì)被調(diào)用而且只有一次勺良。下面先上一個(gè)小demo調(diào)試看看:
上圖里面,我創(chuàng)建了一個(gè)person
類骄噪,以及它的兩個(gè)分類--CLPerson+Test/CLPerson+Test2
尚困,然后給它們都加上兩個(gè)類方法(+load/+test
),main.h
里面先不加任何代碼跑跑看链蕊。
從日志看出事甜,雖然整個(gè)工程都沒有import過CLPerson
以及它的兩個(gè)分類,但是他們的load
方法還是被調(diào)用了滔韵,并且都發(fā)生在main
函數(shù)開始之前逻谦,而且+test
并沒有被調(diào)用。所以該現(xiàn)象間接證明了陪蜻,load
方法的調(diào)用應(yīng)該和類對(duì)象以及分類的加載有關(guān)跨跨。
在main.h
里面調(diào)一下+test
方法
接下來通過源碼分析一下(Runtime源碼下載地址)
首先勇婴,進(jìn)入Runtime的初始化文件objc-os.mm
,找到_objc_init
函數(shù)嘱腥,該函數(shù)可以看作是Runtime的初始化函數(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);
}
忽略一些與本文主題關(guān)聯(lián)不太的函數(shù),直接看最后句_dyld_objc_notify_register(&map_images, load_images, unmap_image);
其中很明顯齿兔,load_images
就是加載鏡像/加載模塊的意思橱脸,應(yīng)該是與我們?cè)掝}相關(guān)的參數(shù),點(diǎn)進(jìn)去看看它的實(shí)現(xiàn)
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
蘋果對(duì)該函數(shù)官方給出的注釋是分苇,處理那些正在進(jìn)行映射的鏡像(images)的+load方法添诉。該方法的實(shí)現(xiàn)里面,做了兩件事情:
-
prepare_load_methods
// Discover load methods -- 查找并準(zhǔn)備load方法医寿,以供后面去調(diào)用 -
call_load_methods();
//Call +load methods -- 調(diào)用這些load方法
針對(duì)上面案例日志中出現(xiàn)的現(xiàn)象栏赴,先從結(jié)果出發(fā),逆向分析靖秩,來看看load方法是如何調(diào)用的须眷,進(jìn)入call_load_methods();
的實(shí)現(xiàn)
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
調(diào)用所有的處理中的class和category的+load方法;
* Class +load methods are called superclass-first.
class的+load方法會(huì)被先調(diào)用沟突,并且花颗,一個(gè)調(diào)用一個(gè)class的+load方法前,會(huì)先對(duì)其父類的+load進(jìn)行調(diào)用
* Category +load methods are not called until after the parent class's +load.
category的+load方法的調(diào)用惠拭,會(huì)發(fā)生在所有的class的+load方法完成調(diào)用之后扩劝。
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:調(diào)用順序
* 1. Repeatedly call class +loads until there aren't any more
遍歷所有的class對(duì)象,調(diào)用它們的+load方法,知道所有class中的+load都完成了調(diào)用
* 2. Call category +loads ONCE.
調(diào)用所有category中的+load方法
* 3. Run more +loads if:
這里我還不太理解棒呛,感覺上面都已經(jīng)把所有的+load調(diào)用完了葡公,還不太理解哪里會(huì)產(chǎn)生新的+load方法。有待繼續(xù)補(bǔ)充......
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
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();
}
// 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;
}
很明顯条霜,核心邏輯在do-while循環(huán)里面催什,循環(huán)中面做了兩件事:
- 首先調(diào)用類對(duì)象的
+load
方法--call_class_loads();
,直到可加載的類的計(jì)數(shù)器減到0 --loadable_classes_used > 0
。 - 然后調(diào)用分類的
+load
方法--call_category_loads();//Call category +loads ONCE
小結(jié)A -- 程序啟動(dòng)之后宰睡,Runtime會(huì)在鏡像加載階段蒲凶,先調(diào)用所有類對(duì)象的
+load
方法,然后在調(diào)用所有分類的+load
方法拆内,類對(duì)象與分類之間參與編譯順序旋圆,不會(huì)影響上面的結(jié)論。例如下圖的調(diào)試麸恍,注意編譯順序
這里產(chǎn)生了一個(gè)新的疑問:既然是方法調(diào)用灵巧,為什么category
的+load
方法沒有“覆蓋”類對(duì)象的+load
方法呢?
有關(guān)分類(
category
)中的方法對(duì)類對(duì)象中的同名方法產(chǎn)生的“覆蓋”現(xiàn)象如果還不太清楚,請(qǐng)參考我的Objective-C之Category的底層實(shí)現(xiàn)原理一文抹沪。
接著上面的源碼刻肄,繼續(xù)看看Runtime對(duì)于類對(duì)象和分類+load
到底是如何調(diào)用的。我們先查看call_class_loads();
融欧,這是對(duì)所有類對(duì)象(class
)的+load
方法的調(diào)用邏輯
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;//首先用局部變量loadable_class保存loadable_classes列表
int used = loadable_classes_used;//在用局部變量used保存loadable_classes_used
loadable_classes = nil;//將loadable_classes置空
loadable_classes_allocated = 0;//將loadable_classes_allocated清零
loadable_classes_used = 0;//將loadable_classes_used清零
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {//遍歷classes列表
Class cls = classes[i].cls;//從列表成員里面獲得cls
load_method_t load_method = (load_method_t)classes[i].method;//從列表成員獲取對(duì)應(yīng)cls的+load 的IMP(方法實(shí)現(xiàn))
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);//這里就是對(duì)+load方法的調(diào)用敏弃,注意哦,這是直接的函數(shù)調(diào)用噪馏,不是消息機(jī)制那種哦麦到,這里跟類的方法列表什么沒關(guān)系,直接就是通過+load的IMP進(jìn)行調(diào)用了
}
// Destroy the detached list.
if (classes) free(classes);
}
上面實(shí)現(xiàn)的主要邏輯發(fā)生在for循環(huán)里面欠肾,該for循環(huán)遍歷了一個(gè)叫classes
的列表瓶颠,該列表存儲(chǔ)的是一堆loadable_class
結(jié)構(gòu)體,loadable_class
的定義如下
struct loadable_class {
Class cls; // may be nil
IMP method;
};
每一個(gè)struct loadable_class
變量刺桃,存儲(chǔ)的應(yīng)該就是 一個(gè)類對(duì)象
+ 一個(gè)與該類相關(guān)的方法實(shí)現(xiàn)
粹淋。從loadable_class
這個(gè)命名,說明它內(nèi)部的信息肯定是表示一個(gè)可以被加載的類的相關(guān)信息虏肾,因此合理推斷廓啊,它里面的method
應(yīng)該就是類的+load
方法欢搜,cls
就是這個(gè)+load
方法所對(duì)應(yīng)的類對(duì)象封豪。這個(gè)推斷是否正確,我們一會(huì)討論炒瘟。
我們?cè)倏纯丛创a中對(duì)于classes
這個(gè)數(shù)組進(jìn)行遍歷時(shí)到底做了什么吹埠。很簡單,就是通過函數(shù)指針load_method
從loadable_class
中獲得+load
方法的IMP
作為其參數(shù),然后就直接對(duì)其進(jìn)行調(diào)用(*load_method)(cls, SEL_load);
缘琅,所以粘都,類對(duì)象的+load
方法的調(diào)用實(shí)際上就發(fā)生在這里。這里的for循環(huán)一旦結(jié)束刷袍,classes
所包含的所有類對(duì)象的+load
方法就會(huì)被依次調(diào)用翩隧,這跟一個(gè)類是否被在工程項(xiàng)目里被實(shí)例化過,是否接受過消息呻纹,沒有半毛錢關(guān)系堆生。
至此,Runtime對(duì)于+load
方法是如何調(diào)用的問題我們分析了一半雷酪,弄清楚了類對(duì)象的+load
方法的是怎么被一個(gè)一個(gè)調(diào)用的淑仆,也就是static void call_class_loads(void)
這個(gè)函數(shù),接下來哥力,還有問題的另一半--static bool call_category_loads(void)
蔗怠,也就是關(guān)于分類的+load
方法的調(diào)用。進(jìn)入其中
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
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++) {
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);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
我們可以看到吩跋,這個(gè)方法的實(shí)現(xiàn)里面寞射,通過系統(tǒng)注釋,被劃分如下幾塊:
- A --
// Detach current loadable list
.分離可加載category
列表锌钮,也就是把可加載列表的信息保存到本函數(shù)的局部變量cats數(shù)組上怠惶。 - B --
// Call all +loads for the detached list
.消費(fèi)cats
里面的所有+load
方法(也就是調(diào)用它們) - C --
// Compact detached list (order-preserving)
清理cats
里面已經(jīng)被消費(fèi)過的成員,并且更新used
計(jì)數(shù)值 - D --
// Copy any new +load candidates from the new list to the detached list.
如果又出現(xiàn)了新的可加載的分類轧粟,將其相關(guān)內(nèi)容復(fù)制到cats
列表上策治。 - E --
// Destroy the new list.
銷毀列表(這里指的是外部的loadable_categories
變量) - F --
// Reattach the (now augmented) detached list. But if there's nothing left to load, destroy the list.
更新幾個(gè)記錄了category+load信息的幾個(gè)全局變量。
相比較于call_class_loads
方法兰吟,這里多了步驟C通惫、D、F混蔼。關(guān)于A履腋、B、E這三個(gè)步驟惭嚣,因?yàn)楦?code>call_class_loads方法里面實(shí)現(xiàn)是一樣的遵湖,不作重復(fù)解釋。且看看多出來的這幾步
先看C
對(duì)于這個(gè)晚吞,我畫個(gè)圖演示一下延旧,就明白了
其實(shí)我感覺消費(fèi)完一輪+load方法之后,cats里面基本上會(huì)在這個(gè)步驟被清空槽地。
然后我們看看D步驟迁沫,如下圖
其實(shí)主要任務(wù)就是把新的可以加載的分類(
category
)信息(如果此時(shí)發(fā)現(xiàn)還有的話)添加到本函數(shù)的cats
數(shù)組上芦瘾。
最后看看F步驟小結(jié)B --
Runtime
對(duì)于+load
方法的調(diào)用,不是走的我們熟悉的“消息發(fā)送”路線集畅,而是直接拿到+load
方法的IMP
近弟,直接調(diào)用。因此不存在所謂“類的方法被category
的方法覆蓋”的問題挺智,所以除了結(jié)論A
的 類與分類的+load
方法先后調(diào)用順序外祷愉,我們看到類與它的分類的所有的+load
全部都被調(diào)用了,沒有被覆蓋赦颇。
目前谣辞,我們確定了類對(duì)象的+load
方法會(huì)先于分類的+load
方法被調(diào)用,并且不存在覆蓋現(xiàn)象沐扳。
- 那么對(duì)于類于類之間
+load
調(diào)用順序是怎樣的泥从? -
同樣的疑問對(duì)于分類(
category
)又是如何呢?
這兩個(gè)問題沪摄,我們就需要進(jìn)入prepare_load_methods
方法的實(shí)現(xiàn)躯嫉,看看+load
方法被調(diào)用前,Runtime是如何準(zhǔn)備它們的杨拐。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
/** ??????????
定制/規(guī)劃類的加載
*/
schedule_class_load(remapClass(classlist[i]));
}
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);
}
}
上面的實(shí)現(xiàn)里祈餐,classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
可以看出,利用系統(tǒng)提供的函數(shù)_getObjc2NonlazyClassList
哄陶,獲得類對(duì)象的列表帆阳,因?yàn)檫@是系統(tǒng)級(jí)別的函數(shù),應(yīng)該跟編譯過程的順序有關(guān)屋吨,這里先推測(cè)classlist
中類的順序與類的編譯順序相同蜒谤。
接下來,就是遍歷classlist
至扰,對(duì)其每個(gè)成員通過函數(shù)schedule_class_load()
進(jìn)行處理
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
/** <#注釋標(biāo)題#>??????????
將cls添加到loadable_classes數(shù)組的最后面
*/
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
該函數(shù)里面就做兩件事:
- 先遞歸調(diào)用自身(
schedule_class_load()
)鳍徽,對(duì)當(dāng)前類(也就是函數(shù)傳入的參數(shù))的父類進(jìn)行處理 - 處理完父類之后,將當(dāng)前類對(duì)象加入到可加載類的相關(guān)列表當(dāng)中
add_class_to_loadable_list(cls);
經(jīng)過這樣的整理之后敢课,最終整理過的裝載類對(duì)象相關(guān)信息的數(shù)組中阶祭,父類應(yīng)該排在子類前面。而不同的類對(duì)象之間在數(shù)組中的位置直秆,就可以參考它們.m的編譯順序來看了濒募。
每個(gè)類對(duì)象在被加入數(shù)組的時(shí)候,會(huì)通過cls->setInfo(RW_LOADED);
設(shè)置標(biāo)簽標(biāo)記一下圾结,這樣瑰剃,如果該類下次被作為父類進(jìn)行遞歸調(diào)用的時(shí)候,就不會(huì)重復(fù)加入到列表中疫稿,保證一個(gè)類在數(shù)組中只出現(xiàn)一次培他。
最后再看一下add_class_to_loadable_list(cls);
里面的邏輯
每個(gè)步驟的作用請(qǐng)看圖中的注釋鹃两。請(qǐng)注意其中一個(gè)細(xì)節(jié)遗座,第三句代碼
method = cls->getLoadMethod();
舀凛,進(jìn)一步查看一下這個(gè)getLoadMethod
很明顯這個(gè)方法就是從一個(gè)類對(duì)象里面尋找load方法實(shí)現(xiàn),找到的話途蒋,就返回load方法的IMP猛遍,賦值給method懊烤。
然后把該類對(duì)象cls
和對(duì)應(yīng)的+load方法IMP method
賦值給loadable_classes
列表最后一個(gè)成員腌紧,該成員我們前面篇章已經(jīng)說了壁肋,該成員就是loadable_class
struct loadable_class {
Class cls; // may be nil
IMP method;
};
我們之前推測(cè)的說loadable_class
里面存放的IMP method;
應(yīng)該就是+load
方法的IMP
浸遗,通過上面的分析跛锌,證明確實(shí)如此髓帽。
上面的是針對(duì)類對(duì)象的+load
的方法所進(jìn)行的調(diào)用前的整理排布氢卡。下面我們看一下分類的+load
方法是如何處理的译秦』魍耄回到prepare_load_methods
方法,這里我直接貼出相關(guān)部分代碼截圖
可以看到,并沒像類一樣械拍,用一個(gè)schedule
方法進(jìn)行遞歸處理,而是直接通過系統(tǒng)函數(shù)_getObjc2NonlazyCategoryList
拿到分類的集合categorylist
甲馋,因?yàn)閷?duì)分類來說定躏,不存在誰是誰的父類痊远,大家都是平級(jí)的碧聪,而且之前類對(duì)象的+load
方法已經(jīng)處理過準(zhǔn)備好了逞姿,所以這里哼凯,只需將categorylist
里面的分類對(duì)象一個(gè)一個(gè)拿出來断部,通過add_category_to_loadable_list
方法處理好,一個(gè)一個(gè)加入到我們后面調(diào)用+load
方法時(shí)所用的loadable_categories
數(shù)組里面。add_category_to_loadable_list(cat)
方法跟上面add_class_to_loadable_list(cls);
方法里面的邏輯完全一致蔑祟,不做重復(fù)解讀趁耗。至此,+load
方法的調(diào)用前的前期準(zhǔn)備工作疆虚,分析完了。
小結(jié)C
- 那么對(duì)于類于類之間
+load
調(diào)用順序是怎樣的径簿?
調(diào)用一個(gè)類對(duì)象的+load
方法之前,會(huì)先調(diào)用其父類的+load
方法(如果存在的話)篇亭,類與類之間缠捌,會(huì)按照編譯的順序译蒂,先后調(diào)用其+load
方法曼月。一個(gè)類對(duì)象的+load
方法不會(huì)被重復(fù)調(diào)用谊却,只可能被調(diào)用一次哑芹。- 同樣的疑問對(duì)于分類(
category
)又是如何呢炎辨?
分類的+load
方法蹦魔,會(huì)按照分類參與編譯的順序招盲,先編譯的,先被調(diào)用讳推。
我們?cè)谕ㄟ^代碼來驗(yàn)證一波顶籽。在開篇案例里面,我繼續(xù)添加幾個(gè)類和分類银觅,CLTeacher
(CLPerson
子類)礼饱、CLTree
(NSObject
子類)、CLRiver
(NSObject
子類)究驴、以及CLTeacher
的兩個(gè)分類镊绪。
- 首先看出,類對(duì)象的
+load
方法肯定是先與所有分類的+load
方法被調(diào)用的洒忧。 - 分類之間是按照編譯的順序蝴韭,先后調(diào)用
+load
。 -
CLTeacher
熙侍、CLTree
榄鉴、CLRiver
也是按照編譯的順序,先后調(diào)用+load
蛉抓,由于CLPerson
是CLTeacher
的父類庆尘,所以會(huì)先用它調(diào)用+load
至此,完全和上面的小結(jié)C吻合芝雪。如果你有興趣减余,可以自己嘗試一下,變換一下源文件的編譯順序惩系,結(jié)果和這里的結(jié)論都是一致的位岔。
到這里如筛,關(guān)于+load
方法調(diào)用的細(xì)節(jié)應(yīng)該就算分析完了~~~
PS:對(duì)于蘋果的源碼,我也在不斷的研讀和學(xué)習(xí)中抒抬,如果文中有闡述不對(duì)的地方杨刨,煩請(qǐng)告知指正,于此與各位共勉~~