在我們的App代碼中,XCode會(huì)自動(dòng)創(chuàng)建一個(gè)main.m文件湾盒,其中定義了main函數(shù)
這里的main
函數(shù)是我們整個(gè)App的入口湿右,它的調(diào)用時(shí)機(jī)甚至?xí)缬?code>AppDelegate 中的didFinishLaunching
回調(diào)。
因此我們會(huì)說(shuō)罚勾,main函數(shù)是我們App程序的入口點(diǎn)函數(shù)
毅人。
那么漾唉,我們App所運(yùn)行的第一個(gè)函數(shù),真的是main函數(shù)
嗎堰塌?如果我們?cè)赬Code中設(shè)置符號(hào)斷點(diǎn)void _objc_init(void)
赵刑,則會(huì)發(fā)現(xiàn),在進(jìn)入main函數(shù)
之前场刑,其實(shí)系統(tǒng)還會(huì)調(diào)用void _objc_init(void)
方法:
這里的_objc_init
方法般此,實(shí)際上是runtime的入口函數(shù)。
void _objc_init(void)
也就是說(shuō)牵现,在App的main函數(shù)之前铐懊,系統(tǒng)會(huì)首先對(duì)App的runtime運(yùn)行環(huán)境,做了一系列的初始化操作
瞎疼。
而這個(gè)runtime入口函數(shù)科乎,又是被誰(shuí)調(diào)用起來(lái)的呢?答案是蘋(píng)果的動(dòng)態(tài)鏈接器dyld(the dynamic link editor)
贼急。dyld是一個(gè)操作系統(tǒng)級(jí)的組件茅茂,它會(huì)負(fù)責(zé)iOS系統(tǒng)中每個(gè)App啟動(dòng)時(shí)的環(huán)境初始化以及動(dòng)態(tài)庫(kù)加載到內(nèi)存等一系列操作。
在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后太抓,交由dyld負(fù)責(zé)余下的工作空闲。
在這里再重申一遍,runtime的入口函數(shù)是_objc_init
走敌,它是在main函數(shù)之前被dyld調(diào)用的碴倾。而+load()方法,則是在main函數(shù)前被_objc_init
調(diào)用掉丽。今天跌榔,我們就來(lái)看一下,在main函數(shù)之前捶障,runtime究竟做了哪些初始化工作僧须。
Mach-O格式
在深入了解_objc_init
的實(shí)現(xiàn)之前,我們需要先了解iOS系統(tǒng)中可執(zhí)行文件的文件格式:Mach-O格式残邀。關(guān)于Mach-O格式皆辽,我們?cè)?a href="http://www.reibang.com/p/59256f511fa6" target="_blank">Objective-C runtime機(jī)制(前傳)——Mach-O格式柑蛇,Objective-C runtime機(jī)制(前傳2)——Mach-O格式和runtime中以及介紹過(guò)芥挣。
我們可以在XCode 工程 product文件夾下找到RuntimeEnter.app工程,用finder打開(kāi)所在目錄耻台,其實(shí)RuntimeEnter.app是一個(gè)壓縮包空免,用鼠標(biāo)右鍵選擇show Package Contents ,可以看到下面有這些文件盆耽,其中和我們工程同名的可運(yùn)行程序就是Mach-O格式的可運(yùn)行文件:
_objc_init
我們?cè)趇OS App中設(shè)置符號(hào)斷點(diǎn)_objc_init
蹋砚,則在App啟動(dòng)時(shí)(進(jìn)入main函數(shù)之前)扼菠,會(huì)進(jìn)入如下調(diào)用堆棧:
可以看到,其底層是由dyld調(diào)用起來(lái)的坝咐。關(guān)于dyld我們不去多說(shuō)循榆,讓我們看一下runtime中_objc_init的定義:
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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);
}
除去上面一堆init方法,我們重點(diǎn)關(guān)注
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
_dyld_objc_notify_register
方法注冊(cè)了對(duì)dyld中關(guān)于加載images的事件回調(diào):
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
分別注冊(cè)了那些事件呢墨坚?根據(jù)注釋秧饮,我們可以知道,共注冊(cè)了三個(gè)事件的回調(diào):
- _dyld_objc_notify_mapped : OC image被加載映射到內(nèi)存(+load()方法在此時(shí)被調(diào)用)
- _dyld_objc_notify_init : OC image被init時(shí)
- _dyld_objc_notify_unmapped : OC image被移除內(nèi)存時(shí)
以上三個(gè)回調(diào)類型是用的函數(shù)指針泽篮,定義為
typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);
_dyld_objc_notify_mapped
當(dāng)image被dyld加載到內(nèi)存后盗尸,會(huì)調(diào)用回調(diào)_dyld_objc_notify_mapped
。在runtime中帽撑,對(duì)應(yīng)的函數(shù)是:
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[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
if (firstTime) {
preopt_init();
}
if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[I];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable's size
#if __OBJC2__
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
#if SUPPORT_GC_COMPAT
// Halt if this is a GC app.
if (shouldRejectGCApp(hi)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"Objective-C garbage collection "
"is no longer supported.");
}
#endif
}
hList[hCount++] = hi;
if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname(),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "",
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
hi->info()->optimizedByDyld()?" (preoptimized)":"");
}
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
sel_init(selrefCount);
arr_init();
#if SUPPORT_GC_COMPAT
// Reject any GC images linked to the main executable.
// We already rejected the app itself above.
// Images loaded after launch will be rejected by dyld.
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[I];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"%s requires Objective-C garbage collection "
"which is no longer supported.", hi->fname());
}
}
#endif
#if TARGET_OS_OSX
// Disable +initialize fork safety if the app is too old (< 10.13).
// Disable +initialize fork safety if the app has a
// __DATA,__objc_fork_ok section.
if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) {
DisableInitializeForkSafety = true;
if (PrintInitializing) {
_objc_inform("INITIALIZE: disabling +initialize fork "
"safety enforcement because the app is "
"too old (SDK version " SDK_FORMAT ")",
FORMAT_SDK(dyld_get_program_sdk_version()));
}
}
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[I];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
DisableInitializeForkSafety = true;
if (PrintInitializing) {
_objc_inform("INITIALIZE: disabling +initialize fork "
"safety enforcement because the app has "
"a __DATA,__objc_fork_ok section");
}
}
break; // assume only one MH_EXECUTE image
}
#endif
}
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
map_images
方法實(shí)質(zhì)上會(huì)調(diào)用map_images_nolock
方法泼各。
而在map_images_nolock
內(nèi)部,又調(diào)用了_read_images
方法亏拉,其核心是用來(lái)讀取Mach-O格式文件的runtime相關(guān)的section信息扣蜻,并轉(zhuǎn)化為runtime內(nèi)部的數(shù)據(jù)結(jié)構(gòu)
。
我們來(lái)看一下_read_images
方法的實(shí)現(xiàn):
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);
runtimeLock.assertWriting();
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break;
}
}
# endif
# if TARGET_OS_OSX
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app is too old (SDK version " SDK_FORMAT ")",
FORMAT_SDK(dyld_get_program_sdk_version()));
}
}
// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
for (EACH_HEADER) {
if (hi->mhdr()->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app has a __DATA,__objc_rawisa section");
}
}
break; // assume only one MH_EXECUTE image
}
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for (EACH_HEADER) {
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
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;
}
}
}
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()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
}
}
ts.log("IMAGE TIMES: remap classes");
// Fix up @selector references
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]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
sel_unlock();
ts.log("IMAGE TIMES: fix up selector references");
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
ts.log("IMAGE TIMES: discover protocols");
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[I]);
}
}
ts.log("IMAGE TIMES: fix up @protocol references");
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif
realizeClass(cls);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
realizeClass(resolvedFutureClasses[I]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
ts.log("IMAGE TIMES: realize future classes");
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[I];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
// Category discovery MUST BE LAST to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// Print preoptimization statistics
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
for (EACH_HEADER) {
if (hi->isPreoptimized()) {
_objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
"in %s", hi->fname());
}
else if (hi->info()->optimizedByDyld()) {
_objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
"in %s", hi->fname());
}
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
PreoptTotalClasses++;
if (hi->isPreoptimized()) {
PreoptOptimizedClasses++;
}
const method_list_t *mlist;
if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
}
}
_objc_inform("PREOPTIMIZATION: %zu selector references not "
"pre-optimized", UnfixedSelectors);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
PreoptOptimizedMethodLists, PreoptTotalMethodLists,
PreoptTotalMethodLists
? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists
: 0.0);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
PreoptOptimizedClasses, PreoptTotalClasses,
PreoptTotalClasses
? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
: 0.0);
_objc_inform("PREOPTIMIZATION: %zu protocol references not "
"pre-optimized", UnfixedProtocolReferences);
}
#undef EACH_HEADER
}
_read_images
方法寫(xiě)了很長(zhǎng)及塘,其實(shí)就是做了一件事弱贼,將Mach-O文件
的section依次讀取,并根據(jù)內(nèi)容初始化runtime的內(nèi)存結(jié)構(gòu)磷蛹。
根據(jù)注釋吮旅,_read_images
方法主要做了下面這些事情:
- 是否需要禁用isa優(yōu)化。這里有三種情況:使用了swift 3.0前的swift代碼味咳。OSX版本早于10.11庇勃。在OSX系統(tǒng)下,Mach-O的DATA段明確指明了__objc_rawisa(不使用優(yōu)化的isa)槽驶。
- 在
__objc_classlist
section中讀取class list - 在
__objc_classrefs
section中讀取class 引用的信息责嚷,并調(diào)用remapClassRef
方法來(lái)處理。 - 在
__objc_selrefs
section中讀取selector的引用信息掂铐,并調(diào)用sel_registerNameNoLock
方法處理罕拂。 - 在
__objc_protolist
section中讀取cls的Protocol信息,并調(diào)用readProtocol
方法來(lái)讀取Protocol信息全陨。 - 在
__objc_protorefs
section中讀取protocol的ref信息爆班,并調(diào)用remapProtocolRef
方法來(lái)處理。 - 在
__objc_nlclslist
section中讀取non-lazy class信息辱姨,并調(diào)用static Class realizeClass(Class cls)
方法來(lái)實(shí)現(xiàn)這些class柿菩。realizeClass
方法核心是初始化objc_class數(shù)據(jù)結(jié)構(gòu),賦予初始值雨涛。 - 在
__objc_catlist
section中讀取category信息枢舶,并調(diào)用addUnattachedCategoryForClass
方法來(lái)為類或元類添加對(duì)應(yīng)的方法懦胞,屬性和協(xié)議。關(guān)于Category凉泄,我們?cè)谇懊娴奈恼轮絮镂荆呀?jīng)有過(guò)相關(guān)的討論。
OK后众,以上就是在dyld將image map到內(nèi)存后醇份,runtime所做的事情:**根據(jù)Mach-O相關(guān)section中的信息,來(lái)初始化runtim的內(nèi)存結(jié)構(gòu)吼具。**
_dyld_objc_notify_init
當(dāng)dyld要init image的時(shí)候僚纷,會(huì)產(chǎn)生_dyld_objc_notify_init
通知。在runime中, 是通過(guò)load_images
方法做回調(diào)響應(yīng)的拗盒。
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
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在load_images方法其實(shí)就是干了一件事怖竭,調(diào)用Class的+load()
方法。
runtime調(diào)用+load()
方法分為兩步走:
- Discover load methods
- Call +load methods
Discover load methods
在Discover load methods中陡蝇,會(huì)調(diào)用prepare_load_methods
來(lái)處理+load
方法:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertWriting();
// 添加 class 到 call +load 隊(duì)列(super class 會(huì)在 subclass之前)
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 添加 category到call +load 隊(duì)列
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);
}
}
prepare_load_methods
邏輯分為兩個(gè)部分:
- 調(diào)用
schedule_class_load
來(lái)組織class的+load
方法 - 調(diào)用
add_category_to_loadable_list
來(lái)組織category的+load
方法
schedule_class_load
方法實(shí)現(xiàn)如下:
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);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
這是一個(gè)遞歸調(diào)用痊臭,會(huì)先把superclass用add_class_to_loadable_list
方法到loadable class list中:
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
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_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
static struct loadable_class *loadable_classes = nil;
從上面的方法可以看出,每一個(gè)定義了+load的類登夫,都會(huì)被放到loadable_classes
中广匙。
因此,+load方法并不存在子類重寫(xiě)父類之說(shuō)恼策。而且父類的+load方法會(huì)先于子類調(diào)用鸦致。
在來(lái)看一下add_category_to_loadable_list
方法:
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// 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_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
static struct loadable_category *loadable_categories = nil;
在add_category_to_loadable_list
方法中,會(huì)將所有定義了+load方法的category都放到loadable_categories
隊(duì)列中涣楷。
Call +load methods
將定義了+load方法的class和category分別放到loadable_classes
和 loadable_categories
隊(duì)列后分唾,runtime就會(huì)依次讀取隊(duì)列中的class和category,并為之調(diào)用+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
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;
}
從上面的call_load_methods
方法可以看出:
- super class的+load方法調(diào)用時(shí)機(jī)是先于sub class的
- class的+load方法調(diào)用時(shí)機(jī)是先于category的
_dyld_objc_notify_unmapped
當(dāng)dyld要將image移除內(nèi)存時(shí)狮斗,會(huì)發(fā)送_dyld_objc_notify_unmapped通知绽乔。在runtime中,是用unmap_image
方法來(lái)響應(yīng)的碳褒。
void
unmap_image(const char *path __unused, const struct mach_header *mh)
{
recursive_mutex_locker_t lock(loadMethodLock);
rwlock_writer_t lock2(runtimeLock);
unmap_image_nolock(mh);
}
void
unmap_image_nolock(const struct mach_header *mh)
{
if (PrintImages) {
_objc_inform("IMAGES: processing 1 newly-unmapped image...\n");
}
header_info *hi;
// Find the runtime's header_info struct for the image
for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
if (hi->mhdr() == (const headerType *)mh) {
break;
}
}
if (!hi) return;
if (PrintImages) {
_objc_inform("IMAGES: unloading image for %s%s%s\n",
hi->fname(),
hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "");
}
_unload_image(hi);
// Remove header_info from header list
removeHeader(hi);
free(hi);
}
主要是做了header信息的移除折砸。
總結(jié)
在本篇文章中,我們知道了dyld在main()函數(shù)之前沙峻,會(huì)調(diào)用runtime的_objc_init
方法睦授。_objc_init
是runtime的入口函數(shù),它會(huì)根據(jù)Mach-O文件中相關(guān)的section信息來(lái)初始化runtime內(nèi)存空間专酗。比如睹逃,加載class,protocol祷肯,以及附加category到class沉填,調(diào)用+load方法等。
當(dāng)然佑笋,在main()函數(shù)前翼闹,dyld除了調(diào)用_objc_init
外,還會(huì)做許多其他的操作蒋纬。如將動(dòng)態(tài)鏈接庫(kù)加載入內(nèi)存猎荠。但這就不屬于runtime的范疇了,我們不去深究蜀备。
當(dāng)dyld將我們App的運(yùn)行環(huán)境都準(zhǔn)備好后关摇,dyld 會(huì)清理現(xiàn)場(chǎng),將調(diào)用椖敫螅回歸输虱,調(diào)用main()函數(shù),這時(shí)候脂凶,我們的App就算啟動(dòng)了:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
在main()函數(shù)被調(diào)用前宪睹,系統(tǒng)其實(shí)已經(jīng)為我們做了很多的準(zhǔn)備工作。就像sunnyxx在其博客中說(shuō)的:
孤獨(dú)的 main 函數(shù)蚕钦,看上去是程序的開(kāi)始亭病,卻是一段精彩的終結(jié)