兩個(gè)方法的區(qū)別
1.兩個(gè)方法的調(diào)用方式
load是拿到函數(shù)地址直接進(jìn)行調(diào)用
initialize是通過objc_msgSend()進(jìn)行調(diào)用的
2.兩個(gè)方法的調(diào)用時(shí)機(jī)
load是runtime加載類瘪撇,分類的時(shí)候調(diào)用的(只調(diào)用一次)
initialize是類第一次接收到消息時(shí)調(diào)用的诗赌,而且每個(gè)類只能被初始化一次(父類initialize方法可能被調(diào)用多次)
3.兩個(gè)方法的調(diào)用順序
-
load
- 先調(diào)用類的load方法
先編譯的類優(yōu)先調(diào)用
調(diào)用子類的load的之前妆兑,會(huì)先調(diào)用父類的load方法 - 再調(diào)用分類的load方法
先編譯的分類優(yōu)先先調(diào)用(只看編譯順序,不區(qū)分是父類的分類還是子類的分類)
- 先調(diào)用類的load方法
-
initialize
先初始化父類
再初始化子類(可能最終調(diào)用的是父類的initialize方法)
load源碼分析步驟:
objc4源碼解讀過程:objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_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(classes);
}
從上面看到+load方法是根據(jù)方法地址直接調(diào)用乳绕,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用,loadable_classes里存放著很多下面這種結(jié)構(gòu)體:
cls也就是哪個(gè)類疯趟,method就是load方法
struct loadable_class {
Class cls;
IMP method;
};
遍歷loadable_classes數(shù)組,拿到load方法地址直接調(diào)用,那么也就是數(shù)組的順序就是調(diào)用load方法的順序叭首,那就接著看看這個(gè)數(shù)組是怎么添加的,看
prepare_load_methods
這個(gè)函數(shù)
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]));
}
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);
}
}
看到這個(gè)函數(shù)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
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
可以看到schedule_class_load(cls->superclass);這行代碼會(huì)遞歸將父類填到到這個(gè)列表踪栋,然后再添加當(dāng)前類焙格,
if (cls->data()->flags & RW_LOADED) return;
這行代碼是判斷是否添加過,如果添加過直接return夷都,保證每個(gè)類只調(diào)用一次load方法
cls->setInfo(RW_LOADED); 這行代碼是將類添加到數(shù)組里時(shí)設(shè)置的標(biāo)識位用來判斷是否添加過
而分類就是正常遍歷直接添加眷唉,所以就是按照編譯順序調(diào)用的,先編譯先調(diào)用
綜上大家應(yīng)該了解了+load方法調(diào)用的底層結(jié)構(gòu)了囤官,如果手動(dòng)調(diào)用[self load]方法冬阳,調(diào)用的還是objc_msgSend(),則按照正常方法調(diào)用步驟,找isa党饮,superclass肝陪,一步步尋找方法進(jìn)行調(diào)用,load方法可能會(huì)被分類覆蓋刑顺,但是一般不需要手動(dòng)調(diào)用见坑,
+initialize源碼分析步驟:
objc4源碼解讀過程
objc-msg-arm64.s
objc_msgSend
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
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
}
retry:
runtimeLock.assertReading();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
- 看上面的源碼可以看到下面片段代碼,會(huì)先判斷當(dāng)前類是否初始化過捏检,initialize參數(shù)系統(tǒng)傳的是YES,所以當(dāng)前類沒初始化過就調(diào)用
_class_initialize()
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
}
_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) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
// 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: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
下面代碼是一部分可以看到荞驴,初始化當(dāng)前類時(shí)會(huì)先判斷父類是否為真,父類是否初始化贯城,如果未初始化去初始化父類熊楼,遞歸調(diào)用。
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
- 父類初始化完成在初始化當(dāng)前類調(diào)用callInitialize能犯,如下面代碼所示,初始化完成調(diào)用lockAndFinishInitializing
callInitialize源碼
- 如下鲫骗,可以看到是掉用的objc_msgSend()
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
lockAndFinishInitializing源碼,將類標(biāo)識為已初始化將flags= RW_INITIALIZED,然后判斷是否初始化代碼:
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
static void lockAndFinishInitializing(Class cls, Class supercls)
{
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
如果你看明白了上面的原理踩晶,看看下面這種情況會(huì)打印什么执泰,如果你還能猜到打印什么,那證明你真的看懂了原理渡蜻,
@interface LSPerson : NSObject
@end
@implementation LSPerson
+ (void)initialize{
NSLog(@"LSPerson +initialize");
}
@end
@interface LSStudent : LSPerson
@end
@implementation LSStudent
+ (void)initialize{
NSLog(@"LSStudent +initialize");
}
@end
@interface LSTeacher : LSPerson
@end
@implementation LSTeacher
+ (void)initialize{
NSLog(@"LSTeacher +initialize");
}
@end
- 1.第一種情況 正常運(yùn)行术吝,結(jié)果在最下面
int main(int argc, const char * argv[]) {
@autoreleasepool {
[LSStudent alloc];
[LSTeacher alloc];
}
return 0;
}
- 2.第二種情況 將 LSStudent里的load方法注釋掉,然后運(yùn)行
int main(int argc, const char * argv[]) {
@autoreleasepool {
[LSStudent alloc];
[LSTeacher alloc];
}
return 0;
}
- 3.第三種情況 將 LSStudent里的load方法注釋掉茸苇,將 LSTeacher里的load方法也注釋掉排苍,然后運(yùn)行
int main(int argc, const char * argv[]) {
@autoreleasepool {
[LSStudent alloc];
[LSTeacher alloc];
}
return 0;
}
- 4.第四種情況 將 LSStudent里的load方法注釋掉,將 LSTeacher里的load方法也注釋掉学密,然后運(yùn)行
int main(int argc, const char * argv[]) {
@autoreleasepool {
[LSStudent initialize];
[LSTeacher initialize];
}
return 0;
}
-
第一種情況打印結(jié)果:都不注釋
LSPerson +initialize
LSStudent +initialize
LSTeacher +initialize
-
第二種情況打印結(jié)果:注釋LSStudent的initialize方法
LSPerson +initialize
LSPerson +initialize
LSTeacher +initialize
-
第三種情況打印結(jié)果:注釋LSStudent的initialize方法淘衙,注釋LSTeacher的initialize方法
LSPerson +initialize
LSPerson +initialize
LSPerson +initialize
-
第四種情況打印結(jié)果:
LSPerson +initialize
LSPerson +initialize
LSPerson +initialize
LSPerson +initialize
LSPerson +initialize
以下是調(diào)用
[LSStudent alloc];
[LSTeacher alloc];
的偽代碼,如果是objc_msgSend([LSStudent class],@selector(initialize)),而student類沒有就會(huì)從父類找腻暮,所以掉了父類的initialize方法彤守,并不是初始化了多次毯侦,teacher也是類似,而第四種情況是具垫,類第一次收到消息觸發(fā)initialize方法侈离,然后所有邏輯都走完之后再去調(diào)用你真正想調(diào)用的initialize方法,所以比第三種情況多2打印了遍
BOOL sutdentInitialized = NO;
BOOL personInitialized = NO;
BOOL teacherInitialized = NO;
if (!sutdentInitialized) {
if (!personInitialized) {
objc_msgSend(LSStudent.superclass, @selector(initialize));
personInitialized = YES;
}
objc_msgSend([LSStudent class], @selector(initialize));
sutdentInitialized = YES;
}
if (!teacherInitialized) {
if (!personInitialized) {
objc_msgSend(LSTeacher.superclass, @selector(initialize));
personInitialized = YES;
}
objc_msgSend([LSTeacher class], @selector(initialize));
teacherInitialized = YES;
}