iOS應(yīng)用 main 執(zhí)行前發(fā)生的事情

這篇是對(duì) iOS 應(yīng)用啟動(dòng)時(shí)箩祥,main 函數(shù)執(zhí)行前發(fā)生的事的一點(diǎn)總結(jié),限于水平,如有錯(cuò)誤請(qǐng)指正~

FAT 二進(jìn)制

FAT 二進(jìn)制文件,將多種架構(gòu)的 Mach-O 文件合并而成县匠。它通過(guò) Fat Header 來(lái)記錄不同架構(gòu)在文件中的偏移量,F(xiàn)at Header 占一頁(yè)(64位16kb撒轮,32位4kb)的空間乞旦。
按分頁(yè)來(lái)存儲(chǔ)這些 segement 和 header 會(huì)浪費(fèi)空間,但這有利于虛擬內(nèi)存的實(shí)現(xiàn)题山。

image

Mach-O 文件

Mach-O為 Mach Object 文件格式的縮寫(xiě)兰粉,它是一種用于可執(zhí)行文件,目標(biāo)代碼顶瞳,動(dòng)態(tài)庫(kù)玖姑,內(nèi)核轉(zhuǎn)儲(chǔ)的文件格式愕秫。
在 Mac OS X 系統(tǒng)中使用 Mach-O 作為其可執(zhí)行文件類(lèi)型。
它的組成結(jié)構(gòu)如下圖所示:

Mach-O 文件結(jié)構(gòu)

每個(gè) Mach-O 文件包括一個(gè) Mach-O Header焰络,然后是一系列的載入命令 load commands戴甩,再是一個(gè)或多個(gè)段(segment),每個(gè)段包括0到255個(gè)節(jié)(section)闪彼。Mach-O使用REL再定位格式控制對(duì)符號(hào)的引用甜孤。Mach-O在兩級(jí)命名空間中將每個(gè)符號(hào)編碼成“對(duì)象-符號(hào)名”對(duì),在查找符號(hào)時(shí)則采用線性搜索法畏腕。

Mach-O包含了幾個(gè) segment缴川,每個(gè) segment 又包含幾個(gè) section。segment的名字都是大寫(xiě)的郊尝,例如__DATA;section的名字都是小寫(xiě)的, 例如 __text二跋。在 Mach-O 的類(lèi)型不為MH_OBJECT時(shí),空間大小為頁(yè)的整數(shù)倍流昏。頁(yè)的大小跟硬件有關(guān)扎即,在 arm64 架構(gòu)一頁(yè)是16kb,其余為4kb况凉。
section 雖然沒(méi)有整數(shù)倍頁(yè)大小的限制谚鄙,但是 section 之間不會(huì)有重疊。

Mach-O Header

推薦使用MachOView這個(gè)軟件查看 Mach-O 的文件結(jié)構(gòu)刁绒。注意需要手動(dòng)關(guān)閉 processing闷营,不然會(huì)閃退。下面是用 MachOView 查看自己的應(yīng)用結(jié)構(gòu):

使用MachOView查看文件結(jié)構(gòu)

東西有點(diǎn)多就沒(méi)有截取全部知市。我們查看一下Mach-O Header部分

Header結(jié)構(gòu)

下面是64位架構(gòu)下header的數(shù)據(jù)結(jié)構(gòu):

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};
image

Mach-O 全部的 filetype 和 flags在loader.h中找到傻盟。


除了MH_OBJECT以外的所有類(lèi)型,段(Segment)的空間大小均為頁(yè)的整數(shù)倍嫂丙。頁(yè)的大小跟硬件有關(guān)系娘赴,在 arm64 架構(gòu)下一頁(yè)為16kb,其它為4kb跟啤。

Load commands

Load commands緊跟在頭部之后, 當(dāng)加載過(guò) header 之后诽表,會(huì)通過(guò)解析Load commands來(lái)加載剩下的數(shù)據(jù),確定其內(nèi)存的分布隅肥。
下面是 load commands 的結(jié)構(gòu)定義:

struct load_command {
    uint32_t cmd;       /* 載入命令類(lèi)型 */
    uint32_t cmdsize;   /* total size of command in bytes */
};

所有load commands的大小即為 Header->sizeofcmds, 共有 Header->ncmds 條load command竿奏。
load command 以LC開(kāi)頭,不同的加載命令有不同的專(zhuān)有的結(jié)構(gòu)體腥放,cmd 和 cmdsize 是都有的泛啸,分別為命令類(lèi)型(即命令名稱(chēng)),這條命令的長(zhǎng)度捉片。這些加載命令告訴系統(tǒng)應(yīng)該如何處理后面的二進(jìn)制數(shù)據(jù)平痰,對(duì)系統(tǒng)內(nèi)核加載器和動(dòng)態(tài)鏈接器起指導(dǎo)作用汞舱。如果當(dāng)前 LC_SEGMENT 包含 section伍纫,那么 section 的結(jié)構(gòu)體緊跟在 LC_SEGMENT 的結(jié)構(gòu)體之后宗雇,所占字節(jié)數(shù)由 SEGMENT 的 cmdsize 字段給出。

cmd(命令名稱(chēng)) 作用
LC_SEGMENT_64 將對(duì)應(yīng)的段中的數(shù)據(jù)加載并映射到進(jìn)程的內(nèi)存空間去
LC_SYMTAB 符號(hào)表信息
LC_DYSYMTAB 動(dòng)態(tài)符號(hào)表信息
LC_LOAD_DYLINKER 啟動(dòng)動(dòng)態(tài)加載連接器/usr/lib/dyld程序
LC_UUID 唯一的 UUID莹规,標(biāo)示該二進(jìn)制文件赔蒲,128bit
LC_VERSION_MIN_IPHONEOS/MACOSX 要求的最低系統(tǒng)版本(Xcode中的Deployment Target)
LC_MAIN 設(shè)置程序主線程的入口地址和棧大小
LC_ENCRYPTION_INFO 加密信息
LC_LOAD_DYLIB 加載的動(dòng)態(tài)庫(kù),包括動(dòng)態(tài)庫(kù)地址良漱、名稱(chēng)舞虱、版本號(hào)等
LC_FUNCTION_STARTS 函數(shù)地址起始表
LC_CODE_SIGNATURE 代碼簽名信息

注意:不同類(lèi)型的 segment 會(huì)使用不同的函數(shù)來(lái)加載

Segment

Mach-O 文件中由許多個(gè)段(Segment),每個(gè)段都有不同的功能母市,每個(gè)段包含了許多個(gè)小的Section矾兜。LC_SEGMENT意味著這部分文件需要映射到進(jìn)程的地址空間去,幾乎所有 Mach-O 都包含這三個(gè)段:

  1. __TEXT:包含了執(zhí)行代碼和其它只讀數(shù)據(jù)(如C 字符串)患久。權(quán)限:只讀(VM_PROT_READ)椅寺,可執(zhí)行(VM_PROT_EXECUTE)
  2. __DATA:程序數(shù)據(jù),包含全局變量蒋失,靜態(tài)變量等返帕。權(quán)限:可讀寫(xiě)(VM_PROT_WRITE/READ) 可執(zhí)行(VM_PROT_EXECUTE)
  3. __LINKEDIT:包含了加載程序的"元數(shù)據(jù)",比如函數(shù)的名稱(chēng)和地址篙挽。權(quán)限:只讀(VM_PROT_READ)

除了上面三個(gè)荆萤,還有一個(gè)常見(jiàn)的 segment:

  • __PAGEZERO:空指針陷阱段,映射到虛擬內(nèi)存空間的第一頁(yè)铣卡,用于捕捉對(duì) NULL 指針的引用

LC_SEGMENT_64 的結(jié)構(gòu)定義為:

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

可以看到這里大部分的成員變量都是幫助內(nèi)核將 segment 映射到虛擬內(nèi)存的链韭。nsects即表明該段中包含多少個(gè) section,section是具體數(shù)據(jù)存放的地方煮落。cmdsize表示當(dāng)前 segment 結(jié)構(gòu)體以及它所包含的所有 section 結(jié)構(gòu)體的總大小敞峭。

文件映射的起始位置由fileoff給出,映射到地址空間的vmaddr處州邢。

Section

section 的名字均為小寫(xiě)儡陨。section 是具體數(shù)據(jù)存放的地方,它的結(jié)構(gòu)體跟隨在 LC_SEGMENT 結(jié)構(gòu)體之后量淌。在64位環(huán)境中它的結(jié)構(gòu)定義為:

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

其中flasg字段儲(chǔ)存了兩個(gè)屬性的值:type 和 attributes骗村。type 只能有一個(gè)值,而 attributes 的值可以有多個(gè)呀枢。如果 segment 中任何一個(gè) section 擁有屬性 S_ATTR_DEBUG胚股,那么該段所有的 section 都會(huì)擁有這個(gè)屬性。屬性詳情可以參考loader.h

section name 作用
__text 主程序代碼
__stub_helper 用于動(dòng)態(tài)鏈接的存根
__symbolstub1 用于動(dòng)態(tài)鏈接的存根
__objc_methname Objective-C 的方法名
__objc_classname Objective-C 的類(lèi)名
__cstring 硬編碼的字符串
__lazy_symbol 懶加載裙秋,延遲加載節(jié)琅拌,通過(guò) dyld_stub_binder 輔助鏈接
_got 存儲(chǔ)引用符號(hào)的實(shí)際地址缨伊,類(lèi)似于動(dòng)態(tài)符號(hào)表
__nl_symbol_ptr 非延遲加載節(jié)
__mod_init_func 初始化的全局函數(shù)地址,在 main 之前被調(diào)用
__mod_term_func 結(jié)束函數(shù)地址
__cfstring Core Foundation 用到的字符串(OC字符串)
__objc_clsslist Objective-C 的類(lèi)列表
__objc_nlclslist Objective-C 的 +load 函數(shù)列表进宝,比 __mod_init_func 更早執(zhí)行
__objc_const Objective-C 的常量
__data 初始化的可變的變量
__bss 未初始化的靜態(tài)變量

虛擬內(nèi)存

在 segment 的結(jié)構(gòu)體中刻坊,我們可以看到vmaddrvmsize兩個(gè)成員變量,它們分別代表 segment 在虛擬內(nèi)存中的地址以及大小党晋。

虛擬內(nèi)存就是一層間接尋址(indirection)谭胚。軟件工程中有句格言就是任何問(wèn)題都能通過(guò)添加一個(gè)間接層來(lái)解決。虛擬內(nèi)存解決的是管理所有進(jìn)程使用物理 RAM 的問(wèn)題未玻。通過(guò)添加間接層來(lái)讓每個(gè)進(jìn)程使用邏輯地址空間灾而,它可以映射到 RAM 上的某個(gè)物理頁(yè)上。這種映射不是一對(duì)一的扳剿,邏輯地址可能映射不到 RAM 上旁趟,也可能有多個(gè)邏輯地址映射到同一個(gè)物理 RAM 上。針對(duì)第一種情況庇绽,當(dāng)進(jìn)程要存儲(chǔ)邏輯地址內(nèi)容時(shí)會(huì)觸發(fā) page fault锡搜;第二種情況就是多進(jìn)程共享內(nèi)存。

對(duì)于文件可以不用一次性讀入整個(gè)文件敛劝,可以使用分頁(yè)映射(mmap())的方式讀取余爆。也就是把文件某個(gè)片段映射到進(jìn)程邏輯內(nèi)存的某個(gè)頁(yè)上。當(dāng)某個(gè)想要讀取的頁(yè)沒(méi)有在內(nèi)存中夸盟,就會(huì)觸發(fā) page fault蛾方,內(nèi)核只會(huì)讀入那一頁(yè),實(shí)現(xiàn)文件的懶加載上陕。

也就是說(shuō) Mach-O 文件中的__TEXT段可以映射到多個(gè)進(jìn)程桩砰,并可以懶加載,且進(jìn)程之間共享內(nèi)存释簿。__DATA段是可讀寫(xiě)的亚隅。這里使用到了Copy-On-Write技術(shù),簡(jiǎn)稱(chēng) COW庶溶。也就是多個(gè)進(jìn)程共享一頁(yè)內(nèi)存空間時(shí)煮纵,一旦有進(jìn)程要做寫(xiě)操作,它會(huì)先將這頁(yè)內(nèi)存內(nèi)容復(fù)制一份出來(lái)偏螺,然后重新映射邏輯地址到新的 RAM 頁(yè)上行疏。也就是這個(gè)進(jìn)程自己擁有了那頁(yè)內(nèi)存的拷貝。這就涉及到了 clean/dirty page 的概念套像。dirty page 含有進(jìn)程自己的信息酿联,而 clean page 可以被內(nèi)核重新生成(重新讀磁盤(pán))。所以 dirty page 的代價(jià)大于 clean page。

在多個(gè)進(jìn)程加載 Mach-O 文件時(shí)__TEXT__LINKEDIT因?yàn)橹蛔x贞让,都是可以共享內(nèi)存的周崭。而__DATA因?yàn)榭勺x寫(xiě),就會(huì)產(chǎn)生 dirty page喳张。當(dāng) dyld 執(zhí)行結(jié)束后续镇,__LINKEDIT就沒(méi)用了,對(duì)應(yīng)的內(nèi)存頁(yè)會(huì)被回收蹲姐。

dyld

dyld

dyld(the dynamic link editor)磨取,Apple 的動(dòng)態(tài)鏈接器人柿。在內(nèi)核完成映射進(jìn)程的工作后會(huì)啟動(dòng)dyld柴墩,負(fù)責(zé)加載應(yīng)用依賴的所有動(dòng)態(tài)鏈接庫(kù),準(zhǔn)備好運(yùn)行所需的一切凫岖。
在 App 啟動(dòng)的時(shí)候江咳,首先會(huì)加載 App 的 mach-o 文件,從 load commands 中得到 dyld 的路徑哥放,并且運(yùn)行歼指。隨后 dyld 做的事情順序概括如下:

  1. 初始化運(yùn)行環(huán)境
  2. 加載主程序執(zhí)行文件 生成 image, 將image添加到一個(gè)全局容器中
  3. 加載共享緩存
  4. 根據(jù)依賴鏈遞歸加載動(dòng)態(tài)鏈接庫(kù) dylib,如果在緩存中有加載好的 image 則直接拿出來(lái)甥雕,否則生成一個(gè)新的 image踩身,將image添加到一個(gè)全局容器中
  5. link 主執(zhí)行文件
  6. link dylib
    • 根據(jù)依賴鏈遞歸修正指針 Rebase
    • 根據(jù)依賴鏈遞歸符號(hào)綁定 Bind
  7. 初始化 dylib(runtime 的初始化就在這個(gè)時(shí)候)

在加載完所有的 dylib 之后,它們處于互相獨(dú)立的狀態(tài)社露,所以還需要將它們綁定起來(lái)挟阻。代碼簽名讓我們不能修改指令,所以不能直接讓一個(gè) dylib 調(diào)用另一個(gè) dylib峭弟,這時(shí)需要很多間接層附鸽。
這個(gè)時(shí)候需要 dyld 來(lái)修正指針(rebasing)和綁定符號(hào)(binding)。

詳細(xì)可以查看 dyld 的源碼中的_main函數(shù)瞒瘸。
下面會(huì)分析上述的其中幾個(gè)步驟坷备。

ImageLoader

ImageLoader是一個(gè)將 mach-o 文件里面二進(jìn)制數(shù)據(jù)(編譯過(guò)的代碼、符號(hào)等)加載到內(nèi)存的基類(lèi)情臭,它負(fù)責(zé)將 mach-o 中的二進(jìn)制數(shù)據(jù)映射到內(nèi)存省撑,它的實(shí)例就是我們熟悉的 image。
每一個(gè) mach-o 文件都會(huì)有一個(gè)對(duì)應(yīng)的 image俯在,實(shí)例的類(lèi)型根據(jù) mach-o 格式的不同也會(huì)不同竟秫。

image
  • ImageLoaderMachOClassic: 用于加載__LINKEDIT段為傳統(tǒng)格式的 mach-o 文件
  • ImageLoaderMachOCompressed: 用于加載__LINKEDIT段為壓縮格式的 mach-o 文件

因?yàn)?code>dylib之間有依賴關(guān)系,所以系統(tǒng)會(huì)沿著依賴鏈遞歸加載 image朝巫。

Rebasing

dylib的二進(jìn)制數(shù)據(jù)會(huì)隨機(jī)的映射到內(nèi)存的一個(gè)隨機(jī)地址ASLR(Address space layout randomization,)中鸿摇,這個(gè)隨機(jī)的地址跟代碼和數(shù)據(jù)指向的舊地址(preferred_address)會(huì)有一定的偏差,dyld需要修正這個(gè)偏差(slide)劈猿,做法就是將dylib內(nèi)部的指針地址都加上這個(gè)偏移值拙吉,偏移值的計(jì)算方法如下:

slide = actual_address - preferred_address

隨后就是不斷的將__DATA段中需要修正的指針加上這個(gè)偏移值潮孽。
注意:每次程序啟動(dòng)后的地址都會(huì)變化,所以指針的地址都需要重新修正筷黔。

在 mach-o 的一個(gè)載入命令LC_DYLD_INFO_ONLY可以查看到rebase, bind, week_bind,lazy_bind的偏移量和大小往史。

image

Binding

binding處理那些指向dylib外部的指針,它們實(shí)際上被符號(hào)名稱(chēng)(symbol)綁定佛舱,也就是個(gè)字符串椎例。比如我們 objc 代碼中需要使用到 NSObject, 即符號(hào)OBJC_CLASS$_NSObject,但是這個(gè)符號(hào)不存在當(dāng)前的 image 中请祖,而是在系統(tǒng)庫(kù) Foundation.framework中订歪,因此就需要binding這個(gè)操作將對(duì)應(yīng)關(guān)系綁定到一起。

Lazy Binding

lazyBinding就是在加載動(dòng)態(tài)庫(kù)的時(shí)候不會(huì)立即 binding, 當(dāng)時(shí)當(dāng)?shù)谝淮握{(diào)用這個(gè)方法的時(shí)候再實(shí)施 binding肆捕。 做到的方法也很簡(jiǎn)單: 通過(guò)dyld_stub_binder這個(gè)符號(hào)來(lái)做刷晋。lazy binding 的方法第一次會(huì)調(diào)用到 dyld_stub_binder, 然后 dyld_stub_binder負(fù)責(zé)找到真實(shí)的方法,并且將地址bind到樁上慎陵,下一次就不用再bind了眼虱。
多數(shù)符號(hào)都是 lazy binding 的

Runtime

每一個(gè)dylib都有自己的初始化方法,當(dāng)相應(yīng)的 image 被加載到內(nèi)存后席纽,就會(huì)調(diào)用初始化方法捏悬。當(dāng)然這不是調(diào)用名為initialize方法,而是C++靜態(tài)對(duì)象初始化構(gòu)造器润梯,__attribute__((constructor))標(biāo)記的方法以及Initializer方法过牙。你可以在程序中設(shè)置環(huán)境變量DYLD_PRINT_INITIALIZERS來(lái)打印dylib的初始化方法。

image
打印信息

我們可以看到程序首先調(diào)用了libSystem這個(gè)dylib的初始化方法仆救。libSystem是很多系統(tǒng)的lib的集合抒和,包括 libdispatch(GCD), libsystem_c(c語(yǔ)言庫(kù)), libsystem_blocks(block)。
libSystem的源碼init.c中我們可以看到彤蔽,它的初始化方法libSystem_initializer會(huì)調(diào)用libdispatch_init();, 然后逐步調(diào)用到_objc_init摧莽,也就是 objc 和 runtime 的入口。
添加一個(gè)符號(hào)斷點(diǎn)_objc_init顿痪,下面是方法調(diào)用棧:

斷點(diǎn)調(diào)試

注意:runtime 和 objc 屬于libobjc


下面是_objc_init的實(shí)現(xiàn):

void _objc_init(void)
{
    // 省略...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

上面的map_images不是將 image 添加到內(nèi)存中的意思镊辕,在這個(gè)方法被調(diào)用的時(shí)候,已經(jīng)完成了 image 的映射以及指針修正蚁袭,綁定符號(hào)的工作了征懈。
這個(gè)函數(shù)實(shí)際上是將 image 中 OBJC 相關(guān)的信息進(jìn)行初始化,具體實(shí)現(xiàn)可以查看_read_image的源碼揩悄,因?yàn)榇a太多所以這里就不貼出來(lái)了卖哎,下面是具體做的事情:

  • 會(huì)將所有的 Class 存放在一張映射類(lèi)名與 Class 的全局表中gdb_objc_realized_classes
  • 隨后調(diào)用readClass函數(shù)將 每一個(gè) Class 添加到gdb_objc_realized_classes表中。
  • 確定 selector 是唯一的
  • read protocols: 讀取protocol
  • realizeClasses:這一步的意義就是動(dòng)態(tài)鏈接好class, 讓class處于可用狀態(tài),主要操作如下:
    • 檢查ro是否已經(jīng)替換為rw,沒(méi)有就替換一下亏娜。
    • 檢查類(lèi)的父類(lèi)和metaClass是否已經(jīng)realize,沒(méi)有就先把它們先realize
    • 重新layout ivar. 因?yàn)橹挥屑虞d好了所有父類(lèi)焕窝,才能確定ivar layout
    • 把一些flags從ro拷貝到rw
    • 鏈接class的 nextSiblingClass 鏈表
    • attach categories: 合并categories的method list、 properties维贺、protocols到 class_rw_t 里面
  • read categories:讀取類(lèi)目

map_images結(jié)束會(huì)調(diào)用load_images函數(shù)它掂。這一步做的事情比較少,就是調(diào)用我們熟悉的+(load)函數(shù)溯泣。父類(lèi)會(huì)先調(diào)用虐秋,除了 Class,每個(gè)類(lèi)目的+(load)方法也會(huì)被調(diào)用一次垃沦,但順序就不一定了客给。

總結(jié)

在這里對(duì) main 函數(shù)之前的操作做一個(gè)小總結(jié)吧:

  1. 將 App 的 mach-o header 讀取到內(nèi)存中
  2. 根據(jù) load commands 獲取 dyld 的路徑,運(yùn)行 dyld
  3. 初始化運(yùn)行環(huán)境栏尚,加載 dylib起愈,如果緩存中存在則從緩存中拿出加載過(guò)的 image,否則新建一個(gè) image译仗,加載到內(nèi)存中
  4. 修正指針(rebase),綁定符號(hào)(bind)
  5. 初始化 dylib官觅,運(yùn)行 runtime
  6. runtime 將 image 中有關(guān) OBJC 的數(shù)據(jù)進(jìn)行初始化
  7. 調(diào)用 +(load) 方法
  8. dyld 調(diào)用 main 函數(shù)

花了一周的時(shí)間用來(lái)研究這部分的內(nèi)容纵菌,終于填完坑了~很舒服
最大的感受就是學(xué)習(xí)完后,看 clang 編譯后的 C++ 代碼能看懂的更多了休涤。比如添加完一個(gè)類(lèi)目之后咱圆,會(huì)將這個(gè)這個(gè)類(lèi)目添加到__DATA的section __objc_catlist中,以前不知道啥意思現(xiàn)在就明白了功氨。也明白 xcode 的許多設(shè)置是用來(lái)干嘛的序苏,總之好處多多~
學(xué)習(xí)也是一個(gè)遞歸的過(guò)程,總之捷凄,也多加油吧忱详!

引用

iOS 程序 main 函數(shù)之前發(fā)生了什么
優(yōu)化 App 的啟動(dòng)時(shí)間
dyld源碼分析-動(dòng)態(tài)加載load
dyld與ObjC

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跺涤,隨后出現(xiàn)的幾起案子匈睁,更是在濱河造成了極大的恐慌,老刑警劉巖桶错,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件航唆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡院刁,警方通過(guò)查閱死者的電腦和手機(jī)糯钙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人任岸,你說(shuō)我怎么就攤上這事鸳玩。” “怎么了演闭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵不跟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我米碰,道長(zhǎng)窝革,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任吕座,我火速辦了婚禮虐译,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吴趴。我一直安慰自己漆诽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布锣枝。 她就那樣靜靜地躺著贾虽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寞射。 梳的紋絲不亂的頭發(fā)上劲藐,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音陨闹,去河邊找鬼楞捂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛趋厉,可吹牛的內(nèi)容都是我干的寨闹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼君账,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼繁堡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起杈绸,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤帖蔓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后瞳脓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體塑娇,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年劫侧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埋酬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哨啃。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖写妥,靈堂內(nèi)的尸體忽然破棺而出拳球,到底是詐尸還是另有隱情,我是刑警寧澤珍特,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布祝峻,位于F島的核電站,受9級(jí)特大地震影響扎筒,放射性物質(zhì)發(fā)生泄漏莱找。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一嗜桌、第九天 我趴在偏房一處隱蔽的房頂上張望奥溺。 院中可真熱鬧,春花似錦骨宠、人聲如沸浮定。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桦卒。三九已至,卻和暖如春棕所,著一層夾襖步出監(jiān)牢的瞬間闸盔,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工琳省, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人躲撰。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓针贬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拢蛋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桦他,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 背景 一個(gè)項(xiàng)目做的時(shí)間長(zhǎng)了,啟動(dòng)流程往往容易雜亂谆棱,庫(kù)也用的越來(lái)越多快压,APP的啟動(dòng)時(shí)間也會(huì)慢慢變長(zhǎng)。本次將針對(duì)iOS...
    醬油瓶2閱讀 3,508評(píng)論 0 12
  • 關(guān)鍵時(shí)刻垃瞧,第一時(shí)間送達(dá)蔫劣! 問(wèn)題種類(lèi) 時(shí)間復(fù)雜度 在集合里數(shù)據(jù)量小的情況下時(shí)間復(fù)雜度對(duì)于性能的影響看起來(lái)微乎其微。但...
    C9090閱讀 893評(píng)論 0 1
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記个从,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間脉幢。...
    MTDeveloper閱讀 748評(píng)論 0 1
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記歪沃,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    茗涙閱讀 1,860評(píng)論 0 3
  • 文/村草 上次出去玩回來(lái)的路上嫌松,跟超子討論一個(gè)話題沪曙,“現(xiàn)在有什么好項(xiàng)目可以投資,并且是投資小見(jiàn)效快的萎羔?” 最關(guān)鍵的...
    村草視角閱讀 320評(píng)論 2 6