load_images
- 進入
load_images
源碼實現,如下
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories(); // 加載所有分類
}
// 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 // +load 方法
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
load_images
方法的主要作用是加載鏡像文件娩践,其中關鍵代碼為 prepare_load_methods
(加載)活翩、call_load_methods
(調用),兩個方法
1. prepare_load_methods
- 進入
prepare_load_methods
的源碼翻伺,實現如下
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count); // 獲取 Mach-O 中的靜態(tài)段 __objc_nlclslist 即非懶加載類
for (i = 0; i < count; i++) {
// 將所有的 +load 方法與類綁定加入 loadable_classes 表中
schedule_class_load(remapClass(classlist[i]));
}
// 獲取 Mach-O 中的靜態(tài)段 __objc_nlcatlist材泄,即非懶加載分類,并 load 方法加入表中
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
- 通過
_getObjc2NonlazyClassList -> schedule_class_load
獲取非懶加載類列表吨岭,將所有的 +load 方法與類綁定加入 loadable_classes 表中拉宗。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
// 確保父類優(yōu)先加載,遞歸未妹,直到父類不存在
schedule_class_load(cls->superclass);
// 將所有的 +load 方法的類跟 load 方法綁定加入 loadable_classes 表中
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
其中簿废,schedule_class_load(cls->superclass);
為了確保它的父類優(yōu)先加載,是一層遞歸循環(huán)络它,直到根類(NSObject)的父類(nil)族檬。add_class_to_loadable_list
是將類的 load
方法和 cls
類名一起加到 loadable_classes
表中,源碼實現如下
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod(); // 獲取類的 load 方法
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) { // 擴容
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
// 加入 loadable_classes 表中
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
獲取類的 load
方法的源碼如下
IMP
objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
ASSERT(isRealized());
ASSERT(ISA()->isRealized());
ASSERT(!isMetaClass());
ASSERT(ISA()->isMetaClass());
mlist = ISA()->data()->ro()->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
if (0 == strcmp(name, "load")) {
return meth.imp;
}
}
}
return nil;
}
通過獲取 ro
中的方法列表化戳,循環(huán)遍歷单料,直到找到方法名為 load
的 sel
,返回 load
的函數指針点楼。
- 通過
_getObjc2NonlazyCategoryList
獲取非懶加載分類列表扫尖,循環(huán)遍歷每個分類。realizeClassWithoutSwift -> add_category_to_loadable_list
將分類的load
方法加入loadable_categories
表中掠廓。add_category_to_loadable_list
的源碼實現如下
- 通過
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat); // 獲取分類的 load 方法
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) { // 擴容
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
// 添加到 loadable_categories 表中
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
2. call_load_methods
源碼實現如下
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)中,主要有三個部分
- 反復調用類的
+load
方法蟀瞧,直到沒有
- 反復調用類的
call_class_loads
的源碼實現如下
- 只調用一次分類的
+load
方法
- 只調用一次分類的
call_category_loads
的源碼實現如下
- 如果有類或更多未嘗試的分類沉颂,則運行更多的+load
在 call_class_loads
和 call_category_loads
中 load 消息發(fā)送 (*load_method)(cls, @selector(load));
有兩個隱藏參數条摸,第一個為 id 即self
,第二個為 sel铸屉,即 cmd
钉蒲。
分類(category)與類擴展(extension)
Category 類別、分類
專門用來給分類添加新的方法
不能給類添加成員屬性彻坛,添加了成員變量顷啼,也無法取到(注意:可以通過 runtime 給分類添加屬性)
分類中用
@property
定義變量,只會生成變量的setter
昌屉,getter
方法的聲明钙蒙,不能生成方法實現和帶下劃線的成員變量
Extension 類擴展
可以說成是特殊的分類,也稱作匿名函數
可以給類添加成員屬性怠益,但是是私有變量
可以給類添加方法仪搔,也是私有方法
類擴展的底層原理探索
創(chuàng)建方式有兩種
- 創(chuàng)建一個
NSObject
類,直接在類中(.m 文件)添加代碼(只能在類的聲明之后蜻牢,實現之前添加)
- 創(chuàng)建一個
- 通過新建(command + N)-> Objective-C File
選擇 Extension
類型烤咧、選擇要添加拓展的主類,創(chuàng)建
類擴展的本質
寫一個類擴展抢呆,如下
通過 clang
底層編譯探索
- 執(zhí)行
clang -rewrite-objc main.mm -o main.cpp
生成 cpp 文件煮嫌,打開 cpp 文件,首先我們看下屬性lg_name
抱虐,搜索lg_name
昌阿,如下
可以看到編譯過程中生成了帶下劃線的成員變量以及 setter、getter 方法恳邀。再來看下類擴展中的方法
從上面我們可以得知懦冰,在編譯的過程中,類擴展中的方法被添加到 methodlist
中成為了類的一部分谣沸,即編譯時期直接添加到本類刷钢。
通過源碼探索
- 創(chuàng)建主類
LGPerson
類,并實現擴展(LGPerson+Ext)中的方法
/** ------.h ------*/
@interface LGPerson : NSObject
@end
/** ------.m ------*/
#import "LGPerson.h"
#import "LGPerson+Ext.h"
@implementation LGPerson
+ (void)load {
}
- (void)ext_instanceMethod {
}
- (void)ext_classMethod {
}
- (void)instanceMethod {
}
- (void)classMethod {
}
@end
/** ------ LGPerson+Ext.h ------*/
#import "LGPerson.h"
@interface LGPerson ()
@property (nonatomic, copy) NSString *lg_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
不導入 LGPerson+Ext.h
運行
objc
源碼乳附,在readClass
打個斷點内地,查看此時的ro
情況
可以看到此時有四個方法,分別打印出來
可以看到此時打印的是 LGPerson.m
中實現的四個方法
- 導入 LGPerson+Ext.h
按照上面的再來一次赋除,看看此時的 ro
情況
方法列表中有 7 個方法阱缓,分別打印出來
此時在拓展類(LGPerson+Ext.h)聲明的屬性也實現 setter
、getter
方法以及一個 .cxx 方法
類的擴展在編譯期間會作為類的一部分举农,和類一起編譯進來
類的擴展只是聲明会宪,依賴于當前的主類坏匪,沒有.m文件