日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

你真的了解load方法么?(转载)

發(fā)布時間:2025/3/15 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你真的了解load方法么?(转载) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文授權轉載,作者:左書祺(關注倉庫,及時獲得更新:iOS-Source-Code-Analyze)

因為 ObjC 的 runtime 只能在 Mac OS 下才能編譯,所以文章中的代碼都是在 Mac OS,也就是 x86_64 架構下運行的,對于在 arm64 中運行的代碼會特別說明。

寫在前面

文章的標題與其說是問各位讀者,不如說是問筆者自己:我真的了解 + load 方法么?

+ load 作為 Objective-C 中的一個方法,與其它方法有很大的不同。它只是一個在整個文件被加載到運行時,在 main 函數調用之前被 ObjC 運行時調用的鉤子方法。其中關鍵字有這么幾個:

  • 文件剛加載

  • main 函數之前

  • 鉤子方法

我在閱讀 ObjC 源代碼之前,曾經一度感覺自己對 + load 方法的作用非常了解,直到看了源代碼中的實現,才知道以前的以為,只是自己的以為罷了。

這篇文章會假設你知道:

  • 使用過 + load 方法

  • 知道 + load 方法的調用順序(文章中會簡單介紹)

在這篇文章中并不會用大篇幅介紹 + load 方法的作用其實也沒幾個作用,關注點主要在以下兩個問題上:

  • + load 方法是如何被調用的

  • + load 方法為什么會有這種調用順序

load 方法的調用棧

首先來通過 load 方法的調用棧,分析一下它到底是如何被調用的。

下面是程序的全部代碼:

1 2 3 4 5 6 7 8 9 10 11 12 //?main.m #import?<foundation foundation.h=""> @interface?XXObject?:?NSObject?@end @implementation?XXObject +?(void)load?{ ????NSLog(@"XXObject?load"); } @end int?main(int?argc,?const?char?*?argv[])?{ ????@autoreleasepool?{?} ????return?0; }</foundation>

代碼總共只實現了一個 XXObject 的 + load 方法,主函數中也沒有任何的東西:

雖然在主函數中什么方法都沒有調用,但是運行之后,依然打印了 XXObject load 字符串,也就是說調用了 + load 方法。

使用符號斷點

使用 Xcode 添加一個符號斷點 +[XXObject load]:

注意這里 + 和 [ 之間沒有空格

為什么要加一個符號斷點呢?因為這樣看起來比較高級。

重新運行程序。這時,代碼會停在 NSLog(@"XXObject load"); 這一行的實現上:

左側的調用棧很清楚的告訴我們,哪些方法被調用了:

1 2 3 4 5 6 0??+[XXObject?load] 1??call_class_loads() 2??call_load_methods 3??load_images 4??dyld::notifySingle(dyld_image_states,?ImageLoader?const*) 11?_dyld_start

dyld?是 the dynamic link editor 的縮寫,它是蘋果的動態(tài)鏈接器。

在系統(tǒng)內核做好程序準備工作之后,交由 dyld 負責余下的工作。本文不會對其進行解釋

每當有新的鏡像加載之后,都會執(zhí)行 3 load_images 方法進行回調,這里的回調是在整個運行時初始化時 _objc_init 注冊的(會在之后的文章中具體介紹):

1 dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized,?0/*not?batch*/,?&load_images);

有新的鏡像被加載到 runtime 時,調用 load_images 方法,并傳入最新鏡像的信息列表 infoList:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const?char?* load_images(enum?dyld_image_states?state,?uint32_t?infoCount, ????????????const?struct?dyld_image_info?infoList[]) { ????bool?found; ????found?=?false; ????for?(uint32_t?i?=?0;?i?<?infoCount;?i++)?{ ????????if?(hasLoadMethods((const?headerType?*)infoList[i].imageLoadAddress))?{ ????????????found?=?true; ????????????break; ????????} ????} ????if?(!found)?return?nil; ????recursive_mutex_locker_t?lock(loadMethodLock); ????{ ????????rwlock_writer_t?lock2(runtimeLock); ????????found?=?load_images_nolock(state,?infoCount,?infoList); ????} ????if?(found)?{ ????????call_load_methods(); ????} ????return?nil; }

什么是鏡像

這里就會遇到一個問題:鏡像到底是什么,我們用一個斷點打印出所有加載的鏡像:

從控制臺輸出的結果大概就是這樣的,我們可以看到鏡像并不是一個 Objective-C 的代碼文件,它應該是一個 target 的編譯產物。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 (const?dyld_image_info)?$52?=?{ ??imageLoadAddress?=?0x00007fff8a144000 ??imageFilePath?=?0x00007fff8a144168?"/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices" ??imageFileModDate?=?1452737802 } (const?dyld_image_info)?$53?=?{ ??imageLoadAddress?=?0x00007fff946d9000 ??imageFilePath?=?0x00007fff946d9480?"/usr/lib/liblangid.dylib" ??imageFileModDate?=?1452737618 } (const?dyld_image_info)?$54?=?{ ??imageLoadAddress?=?0x00007fff88016000 ??imageFilePath?=?0x00007fff88016d40?"/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation" ??imageFileModDate?=?1452737917 } (const?dyld_image_info)?$55?=?{ ??imageLoadAddress?=?0x0000000100000000 ??imageFilePath?=?0x00007fff5fbff8f0?"/Users/apple/Library/Developer/Xcode/DerivedData/objc-dibgivkseuawonexgbqssmdszazo/Build/Products/Debug/debug-objc" ??imageFileModDate?=?0 }

這里面有很多的動態(tài)鏈接庫,還有一些蘋果為我們提供的框架,比如 Foundation、 CoreServices 等等,都是在這個 load_images 中加載進來的,而這些 imageFilePath 都是對應的二進制文件的地址。

但是如果進入最下面的這個目錄,會發(fā)現它是一個可執(zhí)行文件,它的運行結果與 Xcode 中的運行結果相同:

準備 + load 方法

我們重新回到 load_images 方法,如果在掃描鏡像的過程中發(fā)現了 + load 符號:

1 2 3 4 5 6 for?(uint32_t?i?=?0;?i?<?infoCount;?i++)?{ ????if?(hasLoadMethods((const?headerType?*)infoList[i].imageLoadAddress))?{ ????????found?=?true; ????????break; ????} }

就會進入 load_images_nolock 來查找 load 方法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool?load_images_nolock(enum?dyld_image_states?state,uint32_t?infoCount, ???????????????????const?struct?dyld_image_info?infoList[]) { ????bool?found?=?NO; ????uint32_t?i; ????i?=?infoCount; ????while?(i--)?{ ????????const?headerType?*mhdr?=?(headerType*)infoList[i].imageLoadAddress; ????????if?(!hasLoadMethods(mhdr))?continue; ????????prepare_load_methods(mhdr); ????????found?=?YES; ????} ????return?found; }

調用 prepare_load_methods 對 load 方法的調用進行準備(將需要調用 load 方法的類添加到一個列表中,后面的小節(jié)中會介紹):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); ????} }

通過 _getObjc2NonlazyClassList 獲取所有的類的列表之后,會通過 remapClass 獲取類對應的指針,然后調用 schedule_class_load 遞歸地安排當前類和沒有調用 + load 父類進入列表。

1 2 3 4 5 6 7 8 9 static?void?schedule_class_load(Class?cls) { ????if?(!cls)?return; ????assert(cls->isRealized()); ????if?(cls->data()->flags?&?RW_LOADED)?return; ????schedule_class_load(cls->superclass); ????add_class_to_loadable_list(cls); ????cls->setInfo(RW_LOADED);? }

在執(zhí)行 add_class_to_loadable_list(cls) 將當前類加入加載列表之前,會先把父類加入待加載的列表,保證父類在子類前調用 load 方法。

調用 + load 方法

在將鏡像加載到運行時、對 load 方法的準備就緒之后,執(zhí)行 call_load_methods,開始調用 load 方法:

1 2 3 4 5 6 7 8 9 10 11 void?call_load_methods(void) { ????... ????do?{ ????????while?(loadable_classes_used?>?0)?{ ????????????call_class_loads(); ????????} ????????more_categories?=?call_category_loads(); ????}?while?(loadable_classes_used?>?0??||??more_categories); ????... }

方法的調用流程大概是這樣的:

其中 call_class_loads 會從一個待加載的類列表 loadable_classes 中尋找對應的類,然后找到 @selector(load) 的實現并執(zhí)行。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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); }

這行 (*load_method)(cls, SEL_load) 代碼就會調用 +[XXObject load] 方法。

我們會在下面介紹 loadable_classes 列表是如何管理的。

到現在,我們回答了第一個問題:

Q:load 方法是如何被調用的?

A:當 Objective-C 運行時初始化的時候,會通過 dyld_register_image_state_change_handler 在每次有新的鏡像加入運行時的時候,進行回調。執(zhí)行 load_images 將所有包含 load 方法的文件加入列表 loadable_classes ,然后從這個列表中找到對應的 load 方法的實現,調用 load 方法。

加載的管理

ObjC 對于加載的管理,主要使用了兩個列表,分別是 loadable_classes 和 loadable_categories。

方法的調用過程也分為兩個部分,準備 load 方法和調用 load 方法,我更覺得這兩個部分比較像生產者與消費者:

add_class_to_loadable_list 方法負責將類加入 loadable_classes 集合,而 call_class_loads 負責消費集合中的元素。

而對于分類來說,其模型也是類似的,只不過使用了另一個列表 loadable_categories。

“生產” loadable_class

在調用 load_images -> load_images_nolock -> prepare_load_methods -> schedule_class_load -> add_class_to_loadable_list 的時候會將未加載的類添加到 loadable_classes 數組中:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void?add_class_to_loadable_list(Class?cls) { ????IMP?method; ????loadMethodLock.assertLocked(); ????method?=?cls->getLoadMethod(); ????if?(!method)?return; ????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_used].cls?=?cls; ????loadable_classes[loadable_classes_used].method?=?method; ????loadable_classes_used++; }

方法剛被調用時:

  • 會從 class 中獲取 load 方法: method = cls->getLoadMethod();

  • 判斷當前 loadable_classes 這個數組是否已經被全部占用了:loadable_classes_used == loadable_classes_allocated

  • 在當前數組的基礎上擴大數組的大小:realloc

  • 把傳入的 class 以及對應的方法的實現加到列表中

另外一個用于保存分類的列表 loadable_categories 也有一個類似的方法 add_category_to_loadable_list。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void?add_category_to_loadable_list(Category?cat) { ????IMP?method; ????loadMethodLock.assertLocked(); ????method?=?_category_getLoadMethod(cat); ????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++; }

實現幾乎與 add_class_to_loadable_list 完全相同。

到這里我們完成了對 loadable_classes 以及 loadable_categories 的提供,下面會開始消耗列表中的元素。

“消費” loadable_class

調用 load 方法的過程就是“消費” loadable_classes 的過程,load_images -> call_load_methods -> call_class_loads 會從 loadable_classes 中取出對應類和方法,執(zhí)行 load。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void?call_load_methods(void) { ????static?bool?loading?=?NO; ????bool?more_categories; ????loadMethodLock.assertLocked(); ????if?(loading)?return; ????loading?=?YES; ????void?*pool?=?objc_autoreleasePoolPush(); ????do?{ ????????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_categories 中的類以及分類執(zhí)行 load 方法。

1 2 3 4 5 6 do?{ ????while?(loadable_classes_used?>?0)?{ ????????call_class_loads(); ????} ????more_categories?=?call_category_loads(); }?while?(loadable_classes_used?>?0??||??more_categories);

調用順序如下:

  • 不停調用類的 + load 方法,直到 loadable_classes 為空

  • 調用一次 call_category_loads 加載分類

  • 如果有 loadable_classes 或者更多的分類,繼續(xù)調用 load 方法

相比于類 load 方法的調用,分類中 load 方法的調用就有些復雜了:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 static?bool?call_category_loads(void) { ????int?i,?shift; ????bool?new_categories_added?=?NO; ????//?1.?獲取當前可以加載的分類列表 ????struct?loadable_category?*cats?=?loadable_categories; ????int?used?=?loadable_categories_used; ????int?allocated?=?loadable_categories_allocated; ????loadable_categories?=?nil; ????loadable_categories_allocated?=?0; ????loadable_categories_used?=?0; ????for?(i?=?0;?i?<?used;?i++)?{ ????????Category?cat?=?cats[i].cat; ????????load_method_t?load_method?=?(load_method_t)cats[i].method; ????????Class?cls; ????????if?(!cat)?continue; ????????cls?=?_category_getClass(cat); ????????if?(cls??&&??cls->isLoadable())?{ ????????????//?2.?如果當前類是可加載的?`cls??&&??cls->isLoadable()`?就會調用分類的?load?方法 ????????????(*load_method)(cls,?SEL_load); ????????????cats[i].cat?=?nil; ????????} ????} ????//?3.?將所有加載過的分類移除?`loadable_categories`?列表 ????shift?=?0; ????for?(i?=?0;?i?<?used;?i++)?{ ????????if?(cats[i].cat)?{ ????????????cats[i-shift]?=?cats[i]; ????????}?else?{ ????????????shift++; ????????} ????} ????used?-=?shift; ????//?4.?為?`loadable_categories`?重新分配內存,并重新設置它的值 ????new_categories_added?=?(loadable_categories_used?>?0); ????for?(i?=?0;?i?<?loadable_categories_used;?i++)?{ ????????if?(used?==?allocated)?{ ????????????allocated?=?allocated*2?+?16; ????????????cats?=?(struct?loadable_category?*) ????????????????realloc(cats,?allocated?* ??????????????????????????????????sizeof(struct?loadable_category)); ????????} ????????cats[used++]?=?loadable_categories[i]; ????} ????if?(loadable_categories)?free(loadable_categories); ????if?(used)?{ ????????loadable_categories?=?cats; ????????loadable_categories_used?=?used; ????????loadable_categories_allocated?=?allocated; ????}?else?{ ????????if?(cats)?free(cats); ????????loadable_categories?=?nil; ????????loadable_categories_used?=?0; ????????loadable_categories_allocated?=?0; ????} ????return?new_categories_added; }

這個方法有些長,我們來分步解釋方法的作用:

  • 獲取當前可以加載的分類列表

  • 如果當前類是可加載的 cls && cls->isLoadable() 就會調用分類的 load 方法

  • 將所有加載過的分類移除 loadable_categories 列表

  • 為 loadable_categories 重新分配內存,并重新設置它的值

調用的順序

你過去可能會聽說過,對于 load 方法的調用順序有兩條規(guī)則:

  • 父類先于子類調用

  • 類先于分類調用

這種現象是非常符合我們的直覺的,我們來分析一下這種現象出現的原因。

第一條規(guī)則是由于 schedule_class_load 有如下的實現:

1 2 3 4 5 6 7 8 9 static?void?schedule_class_load(Class?cls) { ????if?(!cls)?return; ????assert(cls->isRealized()); ????if?(cls->data()->flags?&?RW_LOADED)?return; ????schedule_class_load(cls->superclass); ????add_class_to_loadable_list(cls); ????cls->setInfo(RW_LOADED);? }

這里通過這行代碼 schedule_class_load(cls->superclass) 總是能夠保證沒有調用 load 方法的父類先于子類加入 loadable_classes 數組,從而確保其調用順序的正確性。

類與分類中 load 方法的調用順序主要在 call_load_methods 中實現:

1 2 3 4 5 6 do?{ ????while?(loadable_classes_used?>?0)?{ ????????call_class_loads(); ????} ????more_categories?=?call_category_loads(); }?while?(loadable_classes_used?>?0??||??more_categories);

上面的 do while 語句能夠在一定程度上確保,類的 load 方法會先于分類調用。但是這里不能完全保證調用順序的正確。

如果分類的鏡像在類的鏡像之前加載到運行時,上面的代碼就沒法保證順序的正確了,所以,我們還需要在 call_category_loads 中判斷類是否已經加載到內存中(調用 load 方法):

1 2 3 4 if?(cls??&&??cls->isLoadable())?{ ????(*load_method)(cls,?SEL_load); ????cats[i].cat?=?nil; }

這里,檢查了類是否存在并且是否可以加載,如果都為真,那么就可以調用分類的 load 方法了。

load 的應用

load 可以說我們在日常開發(fā)中可以接觸到的調用時間最靠前的方法,在主函數運行之前,load 方法就會調用。

由于它的調用不是惰性的,且其只會在程序調用期間調用一次,最最重要的是,如果在類與分類中都實現了 load 方法,它們都會被調用,不像其它的在分類中實現的方法會被覆蓋,這就使 load 方法成為了方法調劑的絕佳時機。

但是由于 load 方法的運行時間過早,所以這里可能不是一個理想的環(huán)境,因為某些類可能需要在在其它類之前加載,但是這是我們無法保證的。不過在這個時間點,所有的 framework 都已經加載到了運行時中,所以調用 framework 中的方法都是安全的。

參考資料

NSObject +load and +initialize - What do they do?

Method Swizzling

Objective-C Class Loading and Initialization

總結

以上是生活随笔為你收集整理的你真的了解load方法么?(转载)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 蜜桃91丨九色丨蝌蚪91桃色 | 波多野结衣家庭主妇 | 性生活视频播放 | 国精产品一品二品国精品69xx | 曰本黄色大片 | 91视频精选 | 欧美日本在线观看 | 国产一区麻豆 | 国产一级片免费播放 | 亚洲性事 | 九九热视频在线播放 | a级黄色小视频 | 性开放视频| 影音先锋 日韩 | 亚洲午夜精品久久 | 婷婷伊人网 | 无码精品黑人一区二区三区 | 天天插天天干天天操 | av在线免费观看不卡 | 免费萌白酱国产一区二区三区 | 亚欧美在线观看 | 日韩有码中文字幕在线 | 日韩欧美一区视频 | 免费观看视频一区二区 | 国产精品资源 | 国产一区视频在线免费观看 | 最新黄色av网址 | 国模私拍视频在线 | 国产国产国产 | 欧美无砖区 | 67194在线免费观看 | 色亭亭 | 欧美一区二区日韩 | 国产免费观看av | 国产三级观看 | 亚洲男女一区二区三区 | 无人在线观看高清视频 单曲 | 色悠悠久久综合 | 美女av免费看 | 亚洲欧美日韩精品久久亚洲区 | 青青青视频免费 | 人人澡超碰碰 | 成人在线手机视频 | 国产一区二区在线播放视频 | 免费看一级 | 性色av网址| 久久岛国 | 靠逼动漫 | 91黄色在线视频 | 国产在线一区二区 | av在线资源 | 成人久久网 | 欧美巨大荫蒂茸毛毛人妖 | 精品动漫一区二区 | 中文字幕一区二区三区手机版 | 大胸喷奶水www视频妖精网站 | 五月婷婷久久久 | 亚洲永久av| 麻豆高清视频 | 麻豆综合网 | 日韩精品视频在线播放 | 国产一区免费看 | 欧美色人阁 | 亚洲国产精品一区二区尤物区 | 久久精品视频观看 | 欧美大波大乳巨大乳 | 亚洲日本中文字幕在线 | 国产一区二区三区四区五区六区 | 熟妇人妻系列aⅴ无码专区友真希 | 国产精品88久久久久久妇女 | 精品国产三级片在线观看 | 好吊一区二区三区视频 | 少妇一区二区三区四区 | 欧洲免费毛片 | 在线观看a级片 | 亚洲每日在线 | 手机av不卡 | 国产成人黄色av | 国产美女主播在线观看 | 竹菊影视日韩一区二区 | 国产精品少妇 | 麻豆短视频在线观看 | 羞羞的视频在线观看 | 超碰人人插 | 亚洲天堂中文在线 | 亚洲av无码不卡一区二区三区 | 欧美日韩成人一区二区在线观看 | 成人国产综合 | 日本成人三级电影 | 中文字幕免| 理想之城连续剧40集免费播放 | 国产1区2区3区4区 | 亚洲在线精品视频 | 久久午夜神器 | 男生插女生的视频 | 久久av一区二区三区漫画 | 精品少妇一二三区 | 黄色av免费在线 | 久久久久久久亚洲精品 |