首發於個人我的博客html
YZPerson.m類git
#import "YZPerson.h"
@implementation YZPerson
+(void)run{
NSLog(@"%s",__func__);
}
+(void)load{
NSLog(@"%s",__func__);
}
@end
複製代碼
YZPerson+test1.m類github
#import "YZPerson+test1.h"
@implementation YZPerson (test1)
+(void)run{
NSLog(@"%s",__func__);
}
+(void)load{
NSLog(@"%s",__func__);
}
@end
複製代碼
YZPerson+test2.m類數組
#import "YZPerson+test2.h"
@implementation YZPerson (test2)
+(void)run{
NSLog(@"%s",__func__);
}
+(void)load{
NSLog(@"%s",__func__);
}
@end
複製代碼
建立完以後,這幾個類不主動調用,直接啓動bash
CateogryDemo[29670:414343] +[YZPerson load]
CateogryDemo[29670:414343] +[YZPerson(test1) load]
CateogryDemo[29670:414343] +[YZPerson(test2) load]
複製代碼
這說明了。load方法,根本不須要咱們本身調用,編譯完成以後,就會調用。app
可是有個疑問,由於,原來的類和分類中都寫了load方法,爲啥都調用呢?爲何不是隻調用分類中的呢?函數
void printMethodNamesOfClass(Class cls)
{
unsigned int count;
// 得到方法數組
Method *methodList = class_copyMethodList(cls, &count);
// 存儲方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍歷全部的方法
for (int i = 0; i < count; i++) {
// 得到方法
Method method = methodList[i];
// 得到方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 釋放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}
複製代碼
- (void)viewDidLoad {
[super viewDidLoad];
printMethodNamesOfClass(object_getClass([YZPerson class]));
}
複製代碼
CateogryDemo[30112:420944] +[YZPerson load]
CateogryDemo[30112:420944] +[YZPerson(test1) load]
CateogryDemo[30112:420944] +[YZPerson(test2) load]
CateogryDemo[30112:420944] YZPerson load, run, load, run, load, run,
複製代碼
看得出來,有三個load方法,三個run方法源碼分析
也進一步驗證了,前面查看源碼分析的結論:合併分類的時候,其方法列表等,不會覆蓋掉原來類中的方法,是共存的。性能
同上,先找到初始化方法ui
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);
}
複製代碼
查看 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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
複製代碼
查看加載load的代碼
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 // 1. 調用類的load方法 while (loadable_classes_used > 0) { call_class_loads(); } // 2. Call category +loads ONCE // 2. 調用分類的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; } 複製代碼
能夠看出,是先調用類的load方法,再調用分類的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);
}
複製代碼
跟着上面註釋中的關鍵代碼,繼續看源碼
typedef void(*load_method_t)(id, SEL);
複製代碼
發現是一個指向函數地址的指針
例如YZStudent是 YZPerson 的子類 YZStudent+test1 是 YZStudent的分類 調用結果爲
CateogryDemo[31904:444099] +[YZPerson load]
CateogryDemo[31904:444099] +[YZStudent load]
CateogryDemo[31904:444099] +[YZStudent(test1) load]
CateogryDemo[31904:444099] +[YZPerson(test1) load]
CateogryDemo[31904:444099] +[YZPerson(test2) 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
{
mutex_locker_t lock2(runtimeLock);
// prepare 準備工做
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
複製代碼
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++) {
// 定製,規劃
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);
}
}
複製代碼
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);
// 將cls 添加到 loadable_classes數組最後面
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
複製代碼
能夠看到,load方法,是遞歸調用,傳入父類。依次加入數組中,那麼調用的時候,先調用父類的load,再調用子類的load,並且和編譯順序無關。
+load方法會在runtime加載類、分類時調用
每一個類、分類的+load,在程序運行過程當中只調用一次 load的調用順序
先調用類的load
再調用分類的load
+initialize方法會在類第一次接收到消息時調用
先調用父類的+initialize,再調用子類的+initialize (先初始化父類,再初始化子類,每一個類只會初始化1次)
runtime源碼 objc4源碼解讀過程
>1. objc-msg-arm64.s
- objc_msgSend
>2. objc-runtime-new.mm
- class_getInstanceMethod
- lookUpImpOrNil
- lookUpImpOrForward
- _class_initialize
- callInitialize
- objc_msgSend(cls, SEL_initialize)
複製代碼
具體來看代碼
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
return _class_getMethod(cls, sel);
}
複製代碼
繼續查看 lookUpImpOrNil
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
複製代碼
lookUpImpOrForward 代碼比較長,摘取關鍵代碼以下
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
}
複製代碼
繼續查看關鍵代碼
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
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;
}
}
.....
callInitialize(cls);
}
複製代碼
這裏能夠看出,若是一個類,其父類沒有初始化,就遞歸調用該方法進行初始化。最終調用callInitialize進行初始化
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
複製代碼
最終,調用到objc_msgSend 方法,那咱們繼續看objc_msgSend源碼,發現是彙編代碼 截取部分以下:
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
#endif
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL
複製代碼
這也說明了,objc_msgSend性能高的緣由,是由於直接操做彙編。
上述流程,用僞代碼表示就是以下,其中 YZStudent 繼承自 YZPerson
if (YZStudent沒有初始化) {
if(YZPerson沒有初始化){
objc_msgSend([YZPerson class],@selector(initialize));
}
objc_msgSend([YZStudent class],@selector(initialize));
}
複製代碼
+initialize和+load的很大區別是,+initialize是經過objc_msgSend進行調用的,因此有如下特色
調用方式
調用時刻
load、initialize的調用順序?
load
先調用類的load
再調用分類的load
initialize
本文相關代碼github地址 github
本文參考資料:
更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。