(一)load方法
- 類及其分類都有
load方法,+load方法會在runtime加載類、分類時調(diào)用 - 每個類、分類的+load,在程序運行過程中只調(diào)用一次
(1)調(diào)用順序
- 先調(diào)用類的+load
- 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
- 調(diào)用子類的+load之前會先調(diào)用父類的+load
- 再調(diào)用分類的+load
- 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
(2)如何查看源碼證明調(diào)用順序?
源碼解讀順序:
(1) objc-os.mm文件
_objc_initload_images
(2) objc-runtime-new.mm文件
load_images-
load_images_nolock尋找\整理load方法 -
call_load_methods調(diào)用load方法
(3)objc-loadmethod.mm文件
-
call_load_methods方法 中 依次調(diào)用call_class_loads和call_category_loads - 查看這兩個方法 即直接依次獲取元類、分類的load方法并加載
重點函數(shù)講解:
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
// 加載元類對象load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
//加載categoty的load方法
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_class_loads和call_category_loads,下面只列舉call_class_loads
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
//這個classes為前面`schedule_class_load`方法通過"整理"出來的數(shù)組
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方法
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.
//調(diào)用load方法
if (classes) free(classes);
}
(3)load方法與自定義方法的區(qū)別
看到這里也大概有點疑惑,為什么分類的自定義方法會“掩蓋”類的原方法,而load不會?
- +load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用
- 自定義方法是經(jīng)過objc_msgSend函數(shù),進行一個消息查找的過程
(4)不同的類load方法調(diào)用順序
上面只搞清楚了類與分類之間的調(diào)用順序,那么不同的類(可能有繼承關(guān)系)調(diào)用順序又是什么樣的呢?
回到前面的方法load_images,在調(diào)用call_load_methods方法前,調(diào)用了一個load_images_nolock方法,里面有一個prepare_load_methods方法,我們看一下:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
//通過編譯順序,獲取的類數(shù)組
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//定制規(guī)劃一些
schedule_class_load(remapClass(classlist[i]));
}
//分類調(diào)用順序 類的load調(diào)用完畢再調(diào)用分類,先編譯先調(diào)用
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);
}
}
我們再看其中的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;
// Ensure superclass-first ordering
//遞歸調(diào)用(優(yōu)先尋找其父類的load方法)
schedule_class_load(cls->superclass);
//將該類添加到loadable_list中
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
這個loadable_list是在后面call_class_loads調(diào)用時用到的,即可說明:
- 不同的類之間的load方法調(diào)用,按編譯順序先后
- 即使子類最初的編譯順序在前,也會優(yōu)先調(diào)用父類的load方法
(默認按照編譯順序,但是要將父類優(yōu)先load) - 分類是按照編譯順序依次調(diào)用
現(xiàn)在我們也能回答前面的面試題了:
4. Category中有l(wèi)oad方法嗎?load方法是什么時候調(diào)用的?load 方法能繼承嗎?
答:有l(wèi)oad方法;load方法是在runtime加載類、分類的時候調(diào)用一次;load方法可以繼承的,但是load方法一般是系統(tǒng)在調(diào)用,一般不會主動調(diào)用
(二)initialize方法
-
+initialize方法會在類第一次接收到消息時調(diào)用(第一次alloc)
(1)調(diào)用順序
- 先調(diào)用父類的
+initialize,再調(diào)用子類的+initialize
- 先初始化父類,再初始化子類,每個類只會初始化1次
(1)如何查看源碼證明調(diào)用順序?
源碼解讀順序:
(1) objc-msg-arm64.s文件
objc_msgSend
(2) objc-runtime-new.mm文件
class_getInstanceMethodlookUpImpOrNillookUpImpOrForward_class_initializecallInitializeobjc_msgSend(cls, SEL_initialize)
(2)+initialize和+load的區(qū)別
-
+initialize是通過objc_msgSend進行調(diào)用的,所以有以下特點 - 如果子類沒有實現(xiàn)
+initialize,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次,但不代表被初始化多次,只是objc_msgSend調(diào)用了父類的initialize方法) - 如果分類實現(xiàn)了
+initialize,就覆蓋類本身的+initialize調(diào)用
類方法調(diào)用:(還是會調(diào)用class_getInstanceMethod)
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);//將自身傳進去
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP imp = nil;
Method meth;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
if (!cls->isRealized()) {
rwlock_writer_t lock(runtimeLock);
realizeClass(cls);
}
//判斷是否需要初始化,且還沒有被初始化過
if (initialize && !cls->isInitialized()) {
//(第一次的時候才會執(zhí)行)
_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
}
....//代碼省略
return imp;
}
我們再看_class_initialize方法:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
BOOL reallyInitialize = NO;
//重點: initialize之前需要保證父類已initialized
// 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) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: calling +[%s initialize]",
cls->nameForLogging());
}
//重點:通過objc_msgSend方法調(diào)用initialize方法
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
cls->nameForLogging());
}
// Done initializing.
// If the superclass is also done initializing, then update
// the info bits and notify waiting threads.
// If not, update them later. (This can happen if this +initialize
// was itself triggered from inside a superclass +initialize.)
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
return;
}
...//代碼省略
}
因此,通過該方法可以看出, 此處是會先調(diào)用父類的initalize,再調(diào)用子類的initalize
load和initialize方法的區(qū)別是什么?
- 調(diào)用方式
- load根據(jù)函數(shù)地址直接調(diào)用
- initialize是通過objc_msgSend調(diào)用
2.調(diào)用時刻 - load是runtime加載類、分類的時候調(diào)用
- initialize是類第一次接受到消息時調(diào)用,且每個類只會被初始化一次(父類的initialize可能會調(diào)用多次,子類可能并未實現(xiàn)initialize方法)
3.調(diào)用順序 - load方法,①先調(diào)用類的load,先編譯的類優(yōu)先調(diào)用load,調(diào)用子類的load之前會調(diào)用父類的load ②其次調(diào)用分類的load,先編譯優(yōu)先調(diào)用
- initialize 先初始化父類,再初始化子類(子類可能因為未實現(xiàn)initialize,通過objc_msgSend調(diào)用父類方法)
ps: 上一期的遺留,同時思考load與initalize方法調(diào)用順序,即原理