<簡書 — 劉小壯> https://www.jianshu.com/p/4fb2d7014e9egit
在iOS程序中會用到不少系統的動態庫,這些動態庫都是動態加載的。全部iOS程序共用一套系統動態庫,在程序開始運行時纔會開始連接動態庫。github
除了在項目設置裏顯式出現的動態庫外,還會有一些隱式存在的動態庫。例如objc
和Runtime
所屬的libobjc.dyld
和libSystem.dyld
,在libSystem
中包含經常使用的libdispatch(GCD)
、libsystem_c
(C語言基礎庫)、libsystem_blocks(Block)
等。數組
使用動態庫的優勢:app
App
公用一套系統動態庫,防止重複的內存佔用。在應用程序啓動後,由dyld(the dynamic link editor)
進行程序的初始化操做。大概流程就像下面列出的步驟,其中第三、四、5步會執行屢次,在ImageLoader
加載新的image
進內存後就會執行一次。函數
dyld
將應用程序加載到二進制中,並完成一些文件的初始化操做。Runtime
向dyld
中註冊回調函數。ImageLoader
將全部image
加載到內存中。dyld
在image
發生改變時,主動調用回調函數。Runtime
接收到dyld
的函數回調,開始執行map_images
、load_images
等操做,並回調+load
方法。main()
函數,開始執行業務代碼。ImageLoader
是image
的加載器,image
能夠理解爲編譯後的二進制。oop
下面是在Runtime
的map_images
函數打斷點,觀察回調狀況的彙編代碼。能夠看出,調用是由dyld
發起的,由ImageLoader
通知dyld
進行調用。源碼分析
關於dyld
我並無深刻研究,有興趣的同窗能夠到Github上下載源碼研究一下。佈局
一個OC程序能夠在運行過程當中動態加載和連接新類或Category
,新類或Category
會加載到程序中,其處理方式和其餘類是相同的。動態加載還能夠作許多不一樣的事,動態加載容許應用程序進行自定義處理。優化
OC提供了objc_loadModules
運行時函數,執行Mach-O
中模塊的動態加載,在上層NSBundle
對象提供了更簡單的訪問API
。ui
在Runtime
加載時,會調用_objc_init
函數,並在內部註冊三個函數指針。其中map_images
函數是初始化的關鍵,內部完成了大量Runtime
環境的初始化操做。
在map_images
函數中,內部也是作了一個調用中轉。而後調用到map_images_nolock
函數,內部核心就是_read_images
函數。
void _objc_init(void)
{
// .... 各類init
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
複製代碼
在_read_images
函數中完成了大量的初始化操做,函數內部代碼量比較大,下面是精簡版帶註釋的源代碼。
先總體梳理一遍_read_images
函數內部的邏輯:
gdb_objc_realized_classes
表中。SEL
都註冊到namedSelectors
表中。Protocol
都添加到protocol_map
表中。Protocol
作重映射。rw
、ro
等操做。Category
,包括Class
和Meta Class
。void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
TimeLogger ts(PrintImageTimes);
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
// 實例化存儲類的哈希表,而且根據當前類數量作動態擴容
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
// 由編譯器讀取類列表,並將全部類添加到類的哈希表中,而且標記懶加載的類並初始化內存空間
for (EACH_HEADER) {
if (! mustReadClasses(hi)) {
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
/** 將新類添加到哈希表中 */
// 從編譯後的類列表中取出全部類,獲取到的是一個classref_t類型的指針
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
// 數組中會取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系統類,例如CF、Fundation、libdispatch中的類。以及本身建立的類
Class cls = (Class)classlist[i];
// 經過readClass函數獲取處理後的新類,內部主要操做ro和rw結構體
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 初始化全部懶加載的類須要的內存空間
if (newCls != cls && newCls) {
// 將懶加載的類添加到數組中
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
// 將未映射Class和Super Class重映射,被remap的類都是非懶加載的類
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 重映射Class,注意是從_getObjc2ClassRefs函數中取出類的引用
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// 重映射父類
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
// 將全部SEL都註冊到哈希表中,是另一張哈希表
static size_t UnfixedSelectors;
sel_lock();
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
// 註冊SEL的操做
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
// 修復舊的函數指針調用遺留
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
for (i = 0; i < count; i++) {
// 內部將經常使用的alloc、objc_msgSend等函數指針進行註冊,並fix爲新的函數指針
fixupMessageRef(refs+i);
}
}
// 遍歷全部協議列表,而且將協議列表加載到Protocol的哈希表中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
// cls = Protocol類,全部協議和對象的結構體都相似,isa都對應Protocol類
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
// 獲取protocol哈希表
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
// 從編譯器中讀取並初始化Protocol
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// 修復協議列表引用,優化後的images多是正確的,可是並不肯定
for (EACH_HEADER) {
// 須要注意到是,下面的函數是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不同
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
// 實現非懶加載的類,對於load方法和靜態實例變量
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 實現全部非懶加載的類(實例化類對象的一些信息,例如rw)
realizeClass(cls);
}
}
// 遍歷resolvedFutureClasses數組,實現全部懶加載的類
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
// 實現懶加載的類
realizeClass(resolvedFutureClasses[i]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
// 發現和處理全部Category
for (EACH_HEADER) {
// 外部循環遍歷找到當前類,查找類對應的Category數組
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 內部循環遍歷當前類的全部Category
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// 首先,經過其所屬的類註冊Category。若是這個類已經被實現,則從新構造類的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 將Category添加到對應Class的value中,value是Class對應的全部category數組
addUnattachedCategoryForClass(cat, cls, hi);
// 將Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
// 這塊和上面邏輯同樣,區別在於這塊是對Meta Class作操做,而上面則是對Class作操做
// 根據下面的邏輯,從代碼的角度來講,是能夠對原類添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
// 初始化從磁盤中加載的全部類,發現Category必須是最後執行的
// 從runtime objc4-532版本源碼來看,DebugNonFragileIvars字段一直是-1,因此不會進入這個方法中
if (DebugNonFragileIvars) {
realizeAllClasses();
}
#undef EACH_HEADER
}
複製代碼
其內部還調用了不少其餘函數,後面會詳細介紹函數內部實現。
在項目中常常用到load
類方法,load
類方法的調用時機比main
函數還要靠前。load
方法是由系統來調用的,而且在整個程序運行期間,只會調用一次,因此能夠在load
方法中執行一些只執行一次的操做。
通常Method Swizzling
都會放在load
方法中執行,這樣在執行main
函數前,就能夠對類方法進行交換。能夠確保正式執行代碼時,方法確定是被交換過的。
若是對一個類添加Category後,而且重寫其原有方法,這樣會致使Category中的方法覆蓋原類的方法。可是load方法倒是例外,全部Category和原類的load方法都會被執行。
load
方法由Runtime
進行調用,下面咱們分析一下load
方法的實現,load
的實現源碼都在objc-loadmethod.mm
中。
在一個新的工程中,咱們建立一個TestObject
類,並在其load
方法中打一個斷點,看一下系統的調用堆棧。
從調用棧能夠看出,是經過系統的動態連接器dyld
開始的調用,而後調到Runtime
的load_images
函數中。load_images
函數是經過_dyld_objc_notify_register
函數,將本身的函數指針註冊給dyld
的。
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
函數中主要作了兩件事,首先經過prepare_load_methods
函數準備Class load list
和Category load list
,而後經過call_load_methods
函數調用已經準備好的兩個方法列表。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!hasLoadMethods((const headerType *)mh)) return;
prepare_load_methods((const headerType *)mh);
call_load_methods();
}
複製代碼
首先咱們看一下prepare_load_methods
函數的實現,看一下其內部是怎麼查找load
方法的。能夠看到,其內部主要分爲兩部分,查找Class
的load
方法列表和查找Category
方法列表。
準備類的方法列表時,首先經過_getObjc2NonlazyClassList
函數獲取全部非懶加載類的列表,這時候獲取到的是一個classref_t
類型的數組,而後遍歷數組添加load
方法列表。
Category
過程也是相似,經過_getObjc2NonlazyCategoryList
函數獲取全部非懶加載Category
的列表,獲得一個category_t
類型的數組,須要注意的是這是一個指向指針的指針。而後對其進行遍歷並添加到load
方法列表,Class
和Category
的load
方法列表是兩個列表。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
// 獲取到非懶加載的類的列表
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 設置Class的調用列表
schedule_class_load(remapClass(classlist[i]));
}
// 獲取到非懶加載的Category列表
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;
// 實例化所屬的類
realizeClass(cls);
// 設置Category的調用列表
add_category_to_loadable_list(cat);
}
}
複製代碼
在添加類的load
方法列表時,內部會遞歸遍歷把全部父類的load
方法都添加進去,順着繼承者鏈的順序添加,會先把父類添加在前面。而後會調用add_class_to_loadable_list
函數,將本身的load
方法添加到對應的數組中。
static void schedule_class_load(Class cls)
{
if (!cls) return;
// 已經添加Class的load方法到調用列表中
if (cls->data()->flags & RW_LOADED) return;
// 確保super已經被添加到load列表中,默認是整個繼承者鏈的順序
schedule_class_load(cls->superclass);
// 將IMP和Class添加到調用列表
add_class_to_loadable_list(cls);
// 設置Class的flags,表示已經添加Class到調用列表中
cls->setInfo(RW_LOADED);
}
複製代碼
而Category
則不須要考慮父類的問題,因此直接在prepare_load_methods
函數中遍歷Category
數組,而後調用add_category_to_loadable_list
函數便可。
在add_category_to_loadable_list
函數中,會判斷當前Category
有沒有實現load
方法,若是沒有則直接return
,若是實現了則添加到loadable_categories
數組中。
類的add_class_to_loadable_list
函數內部實現也是相似,區別在於類的數組叫作loadable_classes
。
void add_category_to_loadable_list(Category cat)
{
IMP method;
// 獲取Category的load方法的IMP
method = _category_getLoadMethod(cat);
// 若是Category沒有load方法則return
if (!method) return;
// 若是已使用大小等於數組大小,對數組進行動態擴容
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_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
複製代碼
到此爲止,loadable_classes
和loadable_categories
兩個數組已經準備好了,load_images
會調用call_load_methods
函數執行這些load
方法。在這個方法中,call_class_loads
函數是負責調用類方法列表的,call_category_loads
負責調用Category
的方法列表。
void call_load_methods(void)
{
bool more_categories;
void *pool = objc_autoreleasePoolPush();
do {
// 反覆執行call_class_loads函數,直到數組中沒有可執行的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
複製代碼
下面是調用類方法列表的代碼,內部主要是經過對loadable_classes
數組進行遍歷,並獲取到loadable_class
的結構體,結構體中存在Class
和IMP
,而後直接調用便可。
Category
的調用方式和類的同樣,就不在下面貼代碼了。須要注意的是,load
方法都是直接調用的,並無走運行時的objc_msgSend
函數。
static void call_class_loads(void)
{
int i;
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
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;
(*load_method)(cls, SEL_load);
}
if (classes) free(classes);
}
struct loadable_class {
Class cls; // may be nil
IMP method;
};
複製代碼
根據上面的源碼分析,咱們能夠看出load
方法的調用順序應該是 「父類 -> 子類 -> 分類」 的順序。由於執行加載Class
的時機是在Category
以前的,並且load
子類以前會先load
父類,因此是這種順序。
和load
方法相似的也有initialize
方法,initialize
方法也是由Runtime
進行調用的,本身不能夠直接調用。與load
方法不一樣的是,initialize
方法是在第一次調用類所屬的方法時,纔會調用initialize
方法,而load
方法是在main
函數以前就所有調用了。因此理論上來講initialize
可能永遠都不會執行,若是當前類的方法永遠不被調用的話。
下面咱們研究一下
initialize
在Runtime
中的源碼。
在向對象發送消息時,lookUpImpOrForward
函數中會判斷當前類是否被初始化,若是沒有被初始化,則先進行初始化再調用類的方法。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
// ....省略好多代碼
// 第一次調用當前類的話,執行initialize的代碼
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
// ....省略好多代碼
複製代碼
在進行初始化的時候,和load
方法的調用順序同樣,會按照繼承者鏈先初始化父類。_class_initialize
函數中關鍵的兩行代碼是callInitialize
和lockAndFinishInitializing
的調用。
// 第一次調用類的方法,初始化類對象
void _class_initialize(Class cls)
{
Class supercls;
bool reallyInitialize = NO;
// 遞歸初始化父類。initizlize不用顯式的調用super,由於runtime已經在內部調用了
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
performForkChildInitialize(cls, supercls);
return;
}
@try {
// 經過objc_msgSend()函數調用initialize方法
callInitialize(cls);
}
@catch (...) {
@throw;
}
@finally {
// 執行initialize方法後,進行系統的initialize過程
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
}
複製代碼
經過objc_msgSend
函數調用initialize
方法。
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
複製代碼
lockAndFinishInitializing
函數中會完成一些初始化操做,其內部會調用_finishInitializing
函數,在函數內部會調用class
的setInitialized
函數,核心工做都由setInitialized
函數完成。
static void lockAndFinishInitializing(Class cls, Class supercls)
{
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
複製代碼
負責初始化類和元類,函數內部主要是查找當前類和元類中是否認義了某些方法,而後根據查找結果設置類和元類的一些標誌位。
void
objc_class::setInitialized()
{
Class metacls;
Class cls;
// 獲取類和元類對象
cls = (Class)this;
metacls = cls->ISA();
bool inherited;
bool metaCustomAWZ = NO;
if (MetaclassNSObjectAWZSwizzled) {
metaCustomAWZ = YES;
inherited = NO;
}
else if (metacls == classNSObject()->ISA()) {
// 查找是否實現了alloc和allocWithZone方法
auto& methods = metacls->data()->methods;
for (auto mlists = methods.beginCategoryMethodLists(),
end = methods.endCategoryMethodLists(metacls);
mlists != end;
++mlists)
{
if (methodListImplementsAWZ(*mlists)) {
metaCustomAWZ = YES;
inherited = NO;
break;
}
}
}
else if (metacls->superclass->hasCustomAWZ()) {
metaCustomAWZ = YES;
inherited = YES;
}
else {
auto& methods = metacls->data()->methods;
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
if (methodListImplementsAWZ(*mlists)) {
metaCustomAWZ = YES;
inherited = NO;
break;
}
}
}
if (!metaCustomAWZ) metacls->setHasDefaultAWZ();
if (PrintCustomAWZ && metaCustomAWZ) metacls->printCustomAWZ(inherited);
bool clsCustomRR = NO;
if (ClassNSObjectRRSwizzled) {
clsCustomRR = YES;
inherited = NO;
}
// 查找元類是否實現MRC方法,若是是則進入if語句中
if (cls == classNSObject()) {
auto& methods = cls->data()->methods;
for (auto mlists = methods.beginCategoryMethodLists(),
end = methods.endCategoryMethodLists(cls);
mlists != end;
++mlists)
{
if (methodListImplementsRR(*mlists)) {
clsCustomRR = YES;
inherited = NO;
break;
}
}
}
else if (!cls->superclass) {
clsCustomRR = YES;
inherited = NO;
}
else if (cls->superclass->hasCustomRR()) {
clsCustomRR = YES;
inherited = YES;
}
else {
// 查找類是否實現MRC方法,若是是則進入if語句中
auto& methods = cls->data()->methods;
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
if (methodListImplementsRR(*mlists)) {
clsCustomRR = YES;
inherited = NO;
break;
}
}
}
if (!clsCustomRR) cls->setHasDefaultRR();
if (PrintCustomRR && clsCustomRR) cls->printCustomRR(inherited);
metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
}
複製代碼
須要注意的是,initialize
方法和load
方法不太同樣,Category
中定義的initialize
方法會覆蓋原方法而不是像load
方法同樣均可以調用。
簡書因爲排版的問題,閱讀體驗並很差,佈局、圖片顯示、代碼等不少問題。因此建議到我Github
上,下載Runtime PDF
合集。把全部Runtime
文章總計九篇,都寫在這個PDF
中,並且左側有目錄,方便閱讀。
下載地址:Runtime PDF 麻煩各位大佬點個贊,謝謝!😁