1. 回顧
在前兩篇博文中半沽,已經(jīng)對dyld
動態(tài)鏈接器的底層源碼進行了探索分析,但是dyld
鏈接images
鏡像文件到內存的過程我們還不知道喘垂,接下來的幾篇博文就著重去探索艰躺。
iOS底層探索之dyld(上):動態(tài)鏈接器流程分析
iOS底層探索之dyld(下):動態(tài)鏈接器流程源碼分析
_objc_init
方法向dyld
中注冊了回調函數(shù),下面就補充一點內容顿天,探究下_objc_init
方法堂氯。
2. _objc_init簡單分析
先來看看
_objc_init
的底層源碼
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();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true ;
#endif
}
-
environ_init()
: 讀取影響運?時的環(huán)境變量。如果需要牌废,還可以打印環(huán)境變量幫助咽白。 -
tls_init()
:關于線程key
的綁定 - ?如每線程數(shù)據(jù)的析構函數(shù) -
static_init()
:運?C ++
靜態(tài)構造函數(shù)。在dyld調?我們的靜態(tài)構造函數(shù)之前鸟缕,libc
會調?_objc_init()
晶框,因此我們必須??做 -
lock_init()
: 沒有重寫,采?C++
的特性 -
exception_init ()
初始化libobjc的異常處理系統(tǒng) -
cache_init()
: 緩存條件初始化 -
runtime_init()
:runtime
運?時環(huán)境初始化,??主要
是:unattachedCategories,allocatedClasses
后?會分析 -
_imp_implementationWithBlock_init
:啟動回調機制。通常這不會做什么懂从,因為所有的初始化都
是惰性的授段,但是對于某些進程,我們會迫不及待地加載trampolines dylib
番甩。
2.1 environ_init
environ_init()
主要代碼如下侵贵,在PrintHelp
或者PrintOptions
條件下,可以在控制臺打印輸出所有的環(huán)境變量缘薛。
void environ_init(void)
{
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if(PrintHelp || PrintOptions) {
...
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
for(size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++){
const option_t *opt = &Settings[i];
if(PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if(PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}
那么現(xiàn)在把if(PrintHelp)
和if(PrintOptions && *opt->var)
判斷條件都去掉模燥,看看控制臺打印了哪些東西。
for(size_t i = 0; i <sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
_objc_inform("%s: %s", opt->env, opt->help);
_objc_inform("%s is set", opt->env);
}
打印如果結果如下:
objc[2964]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[2964]: OBJC_PRINT_IMAGES is set
objc[2964]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[2964]: OBJC_PRINT_IMAGE_TIMES is set
objc[2964]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[2964]: OBJC_PRINT_LOAD_METHODS is set
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS is set
掩宜。蔫骂。。牺汤。辽旋。。省略檐迟。补胚。。追迟。溶其。。
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA is set
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
objc[2964]: OBJC_DISABLE_FAULTS: disable os faults
objc[2964]: OBJC_DISABLE_FAULTS is set
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set
這么多環(huán)境變量敦间,看著就暈瓶逃,有沒有我們熟悉的笆?厢绝?契沫?
有比如:
OBJC_PRINT_IMAGES
打印鏡像文件OBJC_DISABLE_NONPOINTER_ISA
判斷是否是優(yōu)化的指針OBJC_PRINT_LOAD_METHODS
打印出程序中所有的load
方法等等。那么我們沒有
objc
的源碼該如何查看環(huán)境變量呢昔汉?
可以使用終端命令export OBJC_HELP=1
打印如下
2.1.1 終端命令查看環(huán)境變量
2.1.2 Xcode工程設置環(huán)境變量
Xcode
中環(huán)境變量配置的位置:選中運行的target--> Edit scheme... --> Run --> Arguments --> Environment Variables
Xcode設置環(huán)境變量
2.1.2.1 OBJC_DISABLE_NONPOINTER_ISA
設置環(huán)境變量OBJC_DISABLE_NONPOINTER_ISA
表示是否開啟ISA
指針優(yōu)化懈万。YES
表示純指針,NO
表示優(yōu)化后的指針就是nonpointer isa
靶病。
在iOS底層探索之對象的本質和類的關聯(lián)特性initIsa(下)博客中介紹過了ISA
会通。
首先不設置環(huán)境變量看下nonpointer isa
isa
低位0
號位是1
,表示是優(yōu)化后的isa
娄周,而且高位上也有其他的數(shù)據(jù)將環(huán)境變量
OBJC_DISABLE_NONPOINTER_ISA = YES
設置為YES
再看看isa
是怎么樣的涕侈。
isa
低位0
號位是0
,表示是isa
是純指針昆咽,而且高位除了cls
也沒有其他的數(shù)據(jù)了。
2.1.2.2 OBJC_PRINT_LOAD_METHODS
環(huán)境變量OBJC_PRINT_LOAD_METHODS
打印出程序中所有的load
方法牙甫,在自定義類中添加load
方法掷酗,配置環(huán)境變量OBJC_PRINT_LOAD_METHODS = YES
+[JPStudent load]
這個是我們自定義JPStudent
類中的load
方法,其它的都是系統(tǒng)級別的load
方法窟哺。load
方法太多會導致你應用程序啟動變慢泻轰,或者有的人在load
方法里面做一些隱藏的操作,那么就可以通過這個環(huán)境變量檢查出哪個類里面有實現(xiàn)的load
方法且轨。
2.2 tls_init
tls_init
關于線程key
的綁定浮声,比如每個線程數(shù)據(jù)的析構函數(shù)。
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
//創(chuàng)建線程的詹存緩存池
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
//析構函數(shù)
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
2.3 static_init
全局靜態(tài)C++
函數(shù)旋奢,在dyld
調用我們的靜態(tài)構造函數(shù)之前泳挥,libobjc
會調用_objc_init
,會先調用自己的C++
構造函數(shù)至朗,簡單說的就是libobjc
會調用自己的全局的C++
函數(shù)屉符,因為這個全局的構函數(shù)是個非常重要的函數(shù),為了及時性锹引,就自己先調用起來矗钟,從底層源碼的注釋也可以知道,先調用自己的全局靜態(tài)C++
函數(shù)再走dyld
嫌变。
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for(size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for(size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
2.4 runtime_init
runtime
運行時環(huán)境初始化吨艇,里面主要是unattachedCategories
和allocatedClasses
兩張表的初始化。
void runtime_init(void)
{
objc::unattachedCategories.init(32);//分類表的初始化
objc::allocatedClasses.init();//類表的初始化
}
2.5 exception_init
初始化libobjc
庫的異常處理腾啥,這個和objc
向dyld
中注冊回調差不多的意思东涡,就是讓你去處理異常的冯吓。
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
當你的代碼出現(xiàn)crash
,crash
并不是代碼錯誤软啼,不一定會奔潰桑谍。crash
是在上層的編寫的代碼出現(xiàn)一些不符合蘋果系統(tǒng)底層的邏輯和規(guī)則時系統(tǒng)會發(fā)出異常的信號。通過出現(xiàn)異常會進去_objc_terminate
方法祸挪。
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
在_objc_terminate
方法里面發(fā)現(xiàn)了(*uncaught_handler)((id)e)
它會把異常拋出去锣披,全局搜索uncaught_handler
objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
uncaught_handler = fn
告訴程序員可以自己傳一個函數(shù)的句柄,fn
可以是程序員自己定義的函數(shù)贿条,然后回調時可以自己處理程序拋出的異常的信息雹仿。
2.6 cache_t::init
緩存條件的初始化
void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
mach_msg_type_number_t count = 0;
kern_return_t kr;
while (objc_restartableRanges[count].location) {
count++;
}
//開啟緩存
kr = task_restartable_ranges_register(mach_task_self(),
objc_restartableRanges, count)
if (kr == KERN_SUCCESS) return;
_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
2.7 _imp_implementationWithBlock_init
這個是啟動回調機制,通常不會做什么整以。因為所有的初始化都是惰性的胧辽,但是對于某些進程會迫不及待的加載trampolines dylib
,_imp_implementationWithBlock_init
源碼如下:
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
// Eagerly load libobjc-trampolines.dylib in certain processes. Some
// programs (most notably QtWebEngineProcess used by older versions of
// embedded Chromium) enable a highly restrictive sandbox profile which
// blocks access to that dylib. If anything calls
// imp_implementationWithBlock (as AppKit has started doing) then we'll
// crash trying to load it. Loading it here sets it up before the sandbox
// profile is enabled and blocks it.
//
// This fixes EA Origin (rdar://problem/50813789)
// and Steam (rdar://problem/55286131)
if (__progname &&
(strcmp(__progname, "QtWebEngineProcess") == 0 ||
strcmp(__progname, "Steam Helper") == 0)) {
Trampolines.Initialize();
}
#endif
}
2.8 _dyld_objc_notify_register
_dyld_objc_notify_register
向dyld
注冊回調公黑,這個方法非常重要R厣獭!凡蚜!
-
_dyld_objc_notify_register
源碼實現(xiàn)如下:
// _dyld_objc_notify_init
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init
init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it !=
sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
其實在iOS底層探索之dyld(下):動態(tài)鏈接器流程源碼分析 博客中也已經(jīng)介紹了人断,那么我們再來回顧下。
-
&map_images
:dyld
將image
鏡像文件加載到內存中會調用該函數(shù)朝蜘。 -
load_images
:dyld
初始化所有的image
鏡像文件文件會調用恶迈。 -
unmap_image
:將image
鏡像文件移除時會調用。
load_images
方法其實就是調用load
方法谱醇,map_image
方法暇仲,&map_images
是指針傳遞,指向是同一塊實現(xiàn)的地址副渴,如果有什么變化就可以第一時間知道奈附。在dyld
中sNotifyObjCMapped
調用的地方是在notifyBatchPartial
方法中,而notifyBatchPartial
方法是在registerObjCNotifiers
中調用煮剧,在objc
初始化注冊通知時就調用了桅狠,所以是先調用map_images
后調用load_images
這么個函數(shù)執(zhí)行順序。
那么接下就去看看
map_images
3. map_images
3.1 map_images
進入map_images
源碼
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
3.2 map_images_nolock
- 進入
OBJC_PRINT_LOAD_METHODS
源碼
我們要找的無非就是鏡像文件的加載和映射相關的轿秧,其他的代碼就不用看了中跌,直接折疊起來,去重點代碼里面看看菇篡。
3.3 read_images
查看_read_images
方法漩符,發(fā)現(xiàn)代碼太多了,無從下手驱还,只能從全局來分析了嗜暴,于是又把代碼折疊起來凸克。
void _read_images(header_info **hList, uint32_t hCount, int
totalClasses, int
unoptimizedTotalClasses)
{
... //表示省略部分代碼
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// 條件控制進行一次的加載
if (!doneOnce) { ... }
// 修復預編譯階段的`@selector`的混亂的問題
// 就是不同類中有相同的方法 但是相同的方法地址是不一樣的
// Fix up @selector references
static size_t UnfixedSelectors;
{ ... }
ts.log("IMAGE TIMES: fix up selector references");
// 錯誤混亂的類處理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover classes");
// 修復重映射一些沒有被鏡像文件加載進來的類
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) { ... }
ts.log("IMAGE TIMES: remap classes");
#if SUPPORT_FIXUP
// 修復一些消息
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
// 當類中有協(xié)議時:`readProtocol`
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover protocols");
// 修復沒有被加載的協(xié)議
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up @protocol references");
// 分類的處理
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes.
if (didInitialAttachCategories) { ... }
ts.log("IMAGE TIMES: discover categories");
// 類的加載處理
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code befor
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: realize non-lazy classes");
// 沒有被處理的類,優(yōu)化那些被侵犯的類
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) { ... }
ts.log("IMAGE TIMES: realize future classes");
...
#undef EACH_HEADER
}
從大體上可以看出是對一些log
日志的打印輸出闷沥,主要如下:
- 條件控制進行一次加載
- 修復預編譯階段的
@selector
的混亂的問題 - 錯誤混亂的類處理
- 修復重映射一些沒有被鏡像文件加載進來的類
- 修復一些消息
- 當類中有協(xié)議時:
readProtocol
- 修復沒有被加載的協(xié)議
- 分類的處理
- 類的加載處理
- 沒有被處理的類萎战,優(yōu)化那些被侵犯的類
下面就重點分析幾個比較主要的;
3.3.1 doneOnce
doneOnce
if(!doneOnce) {
doneOnce = YES; // 加載一次后舆逃,就不會在進判斷 doneOnce = YES
launchTime = YES;
...
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//創(chuàng)建哈希表 存放所有的類
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
加載一次下次就不會再次進入判斷蚂维。第一次進來主要創(chuàng)建表gdb_objc_realized_classes
,表里存放所有的類不管是實現(xiàn)的還是未實現(xiàn)的都存放在里面路狮,是一張總表虫啥。
3.3.1 UnfixedSelectors
UnfixedSelectors
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
// 從macho文件中獲取方法名列表
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
SEL sel = sel_registerNameNoLock(name, isBundle);//從dyld里面獲取方法名稱
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
不同類中可能存在相同的方法,但是相同的方法地址是不同的.
sels[i]
是_getObjc2SelectorRefs
是從MachO
里面獲取的奄妨,MachO
有相對位移地址和偏移地址涂籽,sel
是sel_registerNameNoLock
從dyld
里面獲取,dyld
是鏈接整個程序的砸抛,所以以dyld
的為準评雌。因為方法是存放在類中兽叮,每個類中的位置是不一樣的匕得,所以方法的地址也就不一樣衔肢,那么就必須對那些混亂的方法進行修復處理逛尚。
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
//從macho中讀取類列表信息
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 類信息發(fā)生混亂,類運行時可能發(fā)生移動辱匿,但是沒有被刪除,相當于常說的野指針
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
cls
指向的是一塊內存地址,newCls
此時還沒有賦值丝里,但是系統(tǒng)會隨機給newCls
分配一塊臟地址,斷點向下走到if
判斷處体谒,再次控制臺lldb
調試看看賦值過后是什么值:
從以上驗證可以看出
readClass
方法的作用:就是把類名和地址關聯(lián)起來杯聚,那么現(xiàn)在去源碼看看
3.3.3 readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
// 獲取類名
const char *mangledName = cls->nonlazyMangledName();
if (missingWeakSuperclass(cls)) { ... }
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) { ... }
if (headerIsPreoptimized && !replacing) {...
} else {
if (mangledName) {
//some Swift generic classes can lazily generate their names
//將類名和地址關聯(lián)起來
addNamedClass(cls, mangledName, replacing);
} else { ...}
//將關聯(lián)的類插入到另一張哈希表中
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) { ... }
return cls;
}
-
nonlazyMangledName
獲取類名。 -
addNamedClass
將類名和地址關聯(lián)綁定起來抒痒。 -
addClassTableEntry
將關聯(lián)的類插入到哈希表中幌绍,這張表中都是初始化過的類。
為了便于研究故响,我們可以對cls
進行過濾傀广,過濾出我們要研究的類JPStudent
。
在此流程中會通過
cls->nonlazyMangledName()
獲取類的名稱彩届,nonlazyMangledName
源碼實現(xiàn)如下:
nonlazyMangledName
// Get the class's mangled name, or NULL if the class has a lazy
// name that hasn't been created yet.
const char *nonlazyMangledName() const {
return bits.safe_ro()->getName();
}
safe_ro
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
-
addNamedClass
將類名和地址關聯(lián)綁定起來
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
通過NXMapInsert
方法更新gdb_objc_realized_classes
哈希表伪冰,key
是name
,value
是cls
-
NXMapInsert
實現(xiàn)部分代碼
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
MapPair *pairs = (MapPair *)table->buckets;
unsigned index = bucketOf(table, key);
MapPair *pair = pairs + index;
if (key == NX_MAPNOTAKEY) {
_objc_inform("*** NXMapInsert: invalid key: -1\n");
return NULL;
}
unsigned numBuckets = table->nbBucketsMinusOne + 1;
if (pair->key == NX_MAPNOTAKEY) {
pair->key = key; pair->value = value;
table->count++;
if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
return NULL;
}
..... 省略代碼 ......
}
addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic
//table already.
// allocatedClasses
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
//將元類插入哈希表中
addClassTableEntry(cls->ISA(), false);
}
allocatedClasses
在_objc_init
中runtime_init
運行時環(huán)境初始化樟蠕,主要是unattachedCategories
和allocatedClasses
兩張表贮聂,此時addClassTableEntry
的操作是插入allocatedClasses
表中靠柑。與此同時還對元類進行了相應的處理,在處理類的時候吓懈,元類也得處理歼冰。
通過源碼分析、斷點調試耻警,發(fā)現(xiàn)對rw
和ro
的獲取和賦值操作隔嫡,并不在readClass
里面,于是又回到_read_images
去看看榕栏。
對
_read_images
方法進行畔勤,斷點跟蹤,一步一步走扒磁,發(fā)現(xiàn)最后走到了realizeClassWithoutSwift
處庆揪,那么進去看看。
-
realizeClassWithoutSwift
源碼如下:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
..... 省略代碼...........
}
好家伙妨托,原來對rw
和ro
的獲取和賦值操作是在realizeClassWithoutSwift
里面進行了處理缸榛,從dyld
到_objc_init
再到read_images
這整個流程就串聯(lián)起來了,接下來的幾篇博客將對類的加載底層原理進行探索分析兰伤!
請持續(xù)關注内颗!敬請期待!
4.總結
- 環(huán)境變量可以通過Xcode設置和終端命令
export OBJC_HELP=1
打印查看 -
_read_images
是對一些log
信息的打印 -
readClass
把類名和地址關聯(lián)起來 -
rw
的賦值和ro
的獲取并不在readClass
里面 -
_dyld_objc_notify_register
執(zhí)行流程圖
_dyld_objc_notify_register執(zhí)行流程圖
更多內容持續(xù)更新
?? 喜歡就點個贊吧????
?? 覺得學習到了的敦腔,可以來一波均澳,收藏+關注,評論 + 轉發(fā)符衔,以免你下次找不到我????
??歡迎大家留言交流找前,批評指正,互相學習??判族,提升自我??