深入理解 iOS App 的啟動(dòng)過(guò)程

前言

啟動(dòng)時(shí)間是衡量應(yīng)用品質(zhì)的重要指標(biāo)。

本文首先會(huì)從原理上出發(fā)威根,講解iOS系統(tǒng)是如何啟動(dòng)App的洛搀,然后從main函數(shù)之前和main函數(shù)之后兩個(gè)角度去分析如何優(yōu)化啟動(dòng)時(shí)間留美。

準(zhǔn)備知識(shí)

Mach-O

哪些名詞指的是Mach-o

  • Executable 可執(zhí)行文件
  • Dylib 動(dòng)態(tài)庫(kù)
  • Bundle 無(wú)法被連接的動(dòng)態(tài)庫(kù)谎砾,只能通過(guò)dlopen()加載
  • Image 指的是Executable,Dylib或者Bundle的一種较雕,文中會(huì)多次使用Image這個(gè)名詞亮蒋。
  • Framework 動(dòng)態(tài)庫(kù)和對(duì)應(yīng)的頭文件和資源文件的集合

Apple出品的操作系統(tǒng)的可執(zhí)行文件格式幾乎都是mach-o宛蚓,iOS當(dāng)然也不例外设塔。

mach-o可以大致的分為三部分:

image
  • Header 頭部闰蛔,包含可以執(zhí)行的CPU架構(gòu)序六,比如x86,arm64
  • Load commands 加載命令例诀,包含文件的組織架構(gòu)和在虛擬內(nèi)存中的布局方式
  • Data繁涂,數(shù)據(jù),包含load commands中需要的各個(gè)段(segment)的數(shù)據(jù)秉沼,每一個(gè)Segment都得大小是Page的整數(shù)倍唬复。

我們用MachOView打開(kāi)Demo工程的可以執(zhí)行文件敞咧,來(lái)驗(yàn)證下mach-o的文件布局:

image

圖中分析的mach-o文件來(lái)源于PullToRefreshKit休建,這是一個(gè)純Swift的編寫的工程丰包。

那么Data部分又包含哪些segment呢?絕大多數(shù)mach-o包括以下三個(gè)段(支持用戶自定義Segment瞧毙,但是很少使用)

  • __TEXT 代碼段宙彪,只讀释漆,包括函數(shù)男图,和只讀的字符串,上圖中類似__TEXT,__text的都是代碼段
  • __DATA 數(shù)據(jù)段,讀寫,包括可讀寫的全局變量等,上圖類似中的__DATA,__data都是數(shù)據(jù)段
  • __LINKEDIT __LINKEDIT包含了方法和變量的元數(shù)據(jù)(位置侦讨,偏移量)崇猫,以及代碼簽名等信息蜡歹。

關(guān)于mach-o更多細(xì)節(jié)议纯,可以看看文檔:《Mac OS X ABI Mach-O File Format Reference》。

dyld

dyld的全稱是dynamic loader,它的作用是加載一個(gè)進(jìn)程所需要的image,dyld是開(kāi)源的捞挥。

Virtual Memory

虛擬內(nèi)存是在物理內(nèi)存上建立的一個(gè)邏輯地址空間胸嘴,它向上(應(yīng)用)提供了一個(gè)連續(xù)的邏輯地址空間劣像,向下隱藏了物理內(nèi)存的細(xì)節(jié)。
虛擬內(nèi)存使得邏輯地址可以沒(méi)有實(shí)際的物理地址,也可以讓多個(gè)邏輯地址對(duì)應(yīng)到一個(gè)物理地址。
虛擬內(nèi)存被劃分為一個(gè)個(gè)大小相同的Page(64位系統(tǒng)上是16KB)庇楞,提高管理和讀寫的效率临燃。 Page又分為只讀和讀寫的Page。</pre>

虛擬內(nèi)存是建立在物理內(nèi)存和進(jìn)程之間的中間層。在iOS上钥勋,當(dāng)內(nèi)存不足的時(shí)候,會(huì)嘗試釋放那些只讀的Page,因?yàn)橹蛔x的Page在下次被訪問(wèn)的時(shí)候先煎,可以再?gòu)拇疟P讀取谤绳。如果沒(méi)有可用內(nèi)存消略,會(huì)通知在后臺(tái)的App(也就是在這個(gè)時(shí)候收到了memory warning)钞艇,如果在這之后仍然沒(méi)有可用內(nèi)存,則會(huì)殺死在后臺(tái)的App。

Page fault

在應(yīng)用執(zhí)行的時(shí)候,它被分配的邏輯地址空間都是可以訪問(wèn)的,當(dāng)應(yīng)用訪問(wèn)一個(gè)邏輯Page,而在對(duì)應(yīng)的物理內(nèi)存中并不存在的時(shí)候,這時(shí)候就發(fā)生了一次Page fault。當(dāng)Page fault發(fā)生的時(shí)候,會(huì)中斷當(dāng)前的程序琳猫,在物理內(nèi)存中尋找一個(gè)可用的Page紊遵,然后從磁盤中讀取數(shù)據(jù)到物理內(nèi)存,接著繼續(xù)執(zhí)行當(dāng)前程序论衍。

Dirty Page & Clean Page

  • 如果一個(gè)Page可以從磁盤上重新生成瘫寝,那么這個(gè)Page稱為Clean Page
  • 如果一個(gè)Page包含了進(jìn)程相關(guān)信息,那么這個(gè)Page稱為Dirty Page

像代碼段這種只讀的Page就是Clean Page。而像數(shù)據(jù)段(_DATA)這種讀寫的Page愁溜,當(dāng)寫數(shù)據(jù)發(fā)生的時(shí)候,會(huì)觸發(fā)COW(Copy on write),也就是寫時(shí)復(fù)制,Page會(huì)被標(biāo)記成Dirty,同時(shí)會(huì)被復(fù)制。

想要了解更多細(xì)節(jié),可以閱讀文檔:Memory Usage Performance Guidelines

啟動(dòng)過(guò)程

使用dyld2啟動(dòng)應(yīng)用的過(guò)程如圖:

image

大致的過(guò)程如下:

加載dyld到App進(jìn)程
加載動(dòng)態(tài)庫(kù)(包括所依賴的所有動(dòng)態(tài)庫(kù))
Rebase
Bind
初始化Objective C Runtime
其它的初始化代碼

加載動(dòng)態(tài)庫(kù)

dyld會(huì)首先讀取mach-o文件的Header和load commands。
接著就知道了這個(gè)可執(zhí)行文件依賴的動(dòng)態(tài)庫(kù)。例如加載動(dòng)態(tài)庫(kù)A到內(nèi)存,接著檢查A所依賴的動(dòng)態(tài)庫(kù)轻绞,就這樣的遞歸加載,直到所有的動(dòng)態(tài)庫(kù)加載完畢。通常一個(gè)App所依賴的動(dòng)態(tài)庫(kù)在100-400個(gè)左右,其中大多數(shù)都是系統(tǒng)的動(dòng)態(tài)庫(kù),它們會(huì)被緩存到dyld shared cache,這樣讀取的效率會(huì)很高趟畏。

查看mach-o文件所依賴的動(dòng)態(tài)庫(kù)沃琅,可以通過(guò)MachOView的圖形化界面(展開(kāi)Load Command就能看到)益眉,也可以通過(guò)命令行otool。

192:Desktop Leo$ otool -L demo
demo:
@rpath/PullToRefreshKit.framework/PullToRefreshKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1444.12.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
@rpath/libswiftCore.dylib (compatibility version 1.0.0, current version 900.0.65)
@rpath/libswiftCoreAudio.dylib (compatibility version 1.0.0, current version 900.0.65)
//...

Rebase && Bind

這里先來(lái)講講為什么要Rebase忍弛?

有兩種主要的技術(shù)來(lái)保證應(yīng)用的安全:ASLR和Code Sign。

ASLR的全稱是Address space layout randomization,翻譯過(guò)來(lái)就是“地址空間布局隨機(jī)化”。App被啟動(dòng)的時(shí)候诫给,程序會(huì)被影射到邏輯的地址空間胃榕,這個(gè)邏輯的地址空間有一個(gè)起始地址换帜,而ASLR技術(shù)使得這個(gè)起始地址是隨機(jī)的递瑰。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函數(shù)的地址慎颗。

Code Sign相信大多數(shù)開(kāi)發(fā)者都知曉倍试,這里要提一點(diǎn)的是谆趾,在進(jìn)行Code sign的時(shí)候跷叉,加密哈希不是針對(duì)于整個(gè)文件,而是針對(duì)于每一個(gè)Page的。這就保證了在dyld進(jìn)行加載的時(shí)候,可以對(duì)每一個(gè)page進(jìn)行獨(dú)立的驗(yàn)證。

mach-o中有很多符號(hào)杨耙,有指向當(dāng)前mach-o的,也有指向其他dylib的,比如printf。那么,在運(yùn)行時(shí)猿妈,代碼如何準(zhǔn)確的找到printf的地址呢俯抖?

mach-o中采用了PIC技術(shù)搔啊,全稱是Position Independ code。當(dāng)你的程序要調(diào)用printf的時(shí)候,會(huì)先在__DATA段中建立一個(gè)指針指向printf,在通過(guò)這個(gè)指針實(shí)現(xiàn)間接調(diào)用迟赃。dyld這時(shí)候需要做一些fix-up工作,即幫助應(yīng)用程序找到這些符號(hào)的實(shí)際地址迄靠。主要包括兩部分

  • Rebase 修正內(nèi)部(指向當(dāng)前mach-o文件)的指針指向
  • Bind 修正外部指針指向
image

之所以需要Rebase吠式,是因?yàn)閯倓偺岬降腁SLR使得地址隨機(jī)化,導(dǎo)致起始地址不固定查辩,另外由于Code Sign,導(dǎo)致不能直接修改Image。Rebase的時(shí)候只需要增加對(duì)應(yīng)的偏移量即可阱佛。待Rebase的數(shù)據(jù)都存放在__LINKEDIT中淮逊。
可以通過(guò)MachOView查看:Dynamic Loader Info -> Rebase Info

也可以通過(guò)命令行:

192:Desktop Leo$ xcrun dyldinfo -bind demo
bind information:
segment section address type addend dylib symbol
__DATA __got 0x10003C038 pointer 0 PullToRefreshKit __T016PullToRefreshKit07DefaultC4LeftC9textLabelSo7UILabelCvWvd
__DATA __got 0x10003C040 pointer 0 PullToRefreshKit __T016PullToRefreshKit07DefaultC5RightC9textLabelSo7UILabelCvWvd
__DATA __got 0x10003C048 pointer 0 PullToRefreshKit __T016PullToRefreshKit07DefaultC6FooterC9textLabelSo7UILabelCvWvd
__DATA __got 0x10003C050 pointer 0 PullToRefreshKit __T016PullToRefreshKit07DefaultC6HeaderC7spinnerSo23UIActivityIndicatorViewCvWvd
//...

Rebase解決了內(nèi)部的符號(hào)引用問(wèn)題备籽,而外部的符號(hào)引用則是由Bind解決。在解決Bind的時(shí)候铸磅,是根據(jù)字符串匹配的方式查找符號(hào)表吹散,所以這個(gè)過(guò)程相對(duì)于Rebase來(lái)說(shuō)是略慢的空民。

同樣界轩,也可以通過(guò)xcrun dyldinfo來(lái)查看Bind的信息浊猾,比如我們查看bind信息中葫慎,包含UITableView的部分:

192:Desktop Leoxcrun dyldinfo -bind demo | grep UITableView __DATA __objc_classrefs 0x100041940 pointer 0 UIKit _OBJC_CLASS__UITableView
__DATA __objc_classrefs 0x1000418B0 pointer 0 UIKit OBJC_CLASS_UITableViewCell __DATA __objc_data 0x100041AC0 pointer 0 UIKit _OBJC_CLASS__UITableViewController
__DATA __objc_data 0x100041BE8 pointer 0 UIKit OBJC_CLASS_UITableViewController __DATA __objc_data 0x100042348 pointer 0 UIKit _OBJC_CLASS__UITableViewController
__DATA __objc_data 0x100042718 pointer 0 UIKit OBJC_CLASS_UITableViewController __DATA __data 0x100042998 pointer 0 UIKit _OBJC_METACLASS__UITableViewController
__DATA __data 0x100042A28 pointer 0 UIKit OBJC_METACLASS_UITableViewController __DATA __data 0x100042F10 pointer 0 UIKit _OBJC_METACLASS__UITableViewController
__DATA __data 0x1000431A8 pointer 0 UIKit OBJC_METACLASS$_UITableViewController

Objective C

Objective C是動(dòng)態(tài)語(yǔ)言昼接,所以在執(zhí)行main函數(shù)之前,需要把類的信息注冊(cè)到一個(gè)全局的Table中慢睡。同時(shí),Objective C支持Category,在初始化的時(shí)候,也會(huì)把Category中的方法注冊(cè)到對(duì)應(yīng)的類中,同時(shí)會(huì)唯一Selector靶庙,這也是為什么當(dāng)你的Cagegory實(shí)現(xiàn)了類中同名的方法后,類中的方法會(huì)被覆蓋砚亭。

另外,由于iOS開(kāi)發(fā)時(shí)基于Cocoa Touch的,所以絕大多數(shù)的類起始都是系統(tǒng)類林艘,所以大多數(shù)的Runtime初始化起始在Rebase和Bind中已經(jīng)完成。

Initializers

接下來(lái)就是必要的初始化部分了,主要包括幾部分:

  • +load方法。
  • C/C++靜態(tài)初始化對(duì)象和標(biāo)記為attribute(constructor)的方法

這里要提一點(diǎn)的就是,+load方法已經(jīng)被棄用了,如果你用Swift開(kāi)發(fā),你會(huì)發(fā)現(xiàn)根本無(wú)法去寫這樣一個(gè)方法,官方的建議是實(shí)用initialize怖喻。區(qū)別就是,load是在類裝載的時(shí)候執(zhí)行,而initialize是在類第一次收到message前調(diào)用。

dylD3

上文的講解是dyld2的加載方式。而最新的是dyld3加載方式略有不同:

image

dyld2是純粹的in-process柿估,也就是在程序進(jìn)程內(nèi)執(zhí)行的足陨,也就意味著只有當(dāng)應(yīng)用程序被啟動(dòng)的時(shí)候,dyld2才能開(kāi)始執(zhí)行任務(wù)。

dyld3則是部分out-of-process,部分in-process鸯檬。圖中蹂楣,虛線之上的部分是out-of-process的赁酝,在App下載安裝和版本更新的時(shí)候會(huì)去執(zhí)行隙袁,out-of-process會(huì)做如下事情:

  • 分析Mach-o Headers
  • 分析依賴的動(dòng)態(tài)庫(kù)
  • 查找需要Rebase & Bind之類的符號(hào)
  • 把上述結(jié)果寫入緩存

這樣,在應(yīng)用啟動(dòng)的時(shí)候坡贺,就可以直接從緩存中讀取數(shù)據(jù)公般,加快加載速度刽虹。

啟動(dòng)時(shí)間

冷啟動(dòng) VS 熱啟動(dòng)

如果你剛剛啟動(dòng)過(guò)App初烘,這時(shí)候App的啟動(dòng)所需要的數(shù)據(jù)仍然在緩存中,再次啟動(dòng)的時(shí)候稱為熱啟動(dòng)。如果設(shè)備剛剛重啟萄喳,然后啟動(dòng)App,這時(shí)候稱為冷啟動(dòng)巡莹。

啟動(dòng)時(shí)間在小于400ms是最佳的腰根,因?yàn)閺狞c(diǎn)擊圖標(biāo)到顯示Launch Screen东帅,到Launch Screen消失這段時(shí)間是400ms点弯。啟動(dòng)時(shí)間不可以大于20s,否則會(huì)被系統(tǒng)殺掉梅掠。

在Xcode中酌住,可以通過(guò)設(shè)置環(huán)境變量來(lái)查看App的啟動(dòng)時(shí)間,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS阎抒。

image

Total pre-main time: 43.00 milliseconds (100.0%)
dylib loading time: 19.01 milliseconds (44.2%)
rebase/binding time: 1.77 milliseconds (4.1%)
ObjC setup time: 3.98 milliseconds (9.2%)
initializer time: 18.17 milliseconds (42.2%)
slowest intializers :
libSystem.B.dylib : 2.56 milliseconds (5.9%)
libBacktraceRecording.dylib : 3.00 milliseconds (6.9%)
libMainThreadChecker.dylib : 8.26 milliseconds (19.2%)
ModelIO : 1.37 milliseconds (3.1%)

對(duì)于這個(gè)libMainThreadChecker.dylib估計(jì)很多同學(xué)會(huì)有點(diǎn)陌生酪我,這是XCode 9新增的動(dòng)態(tài)庫(kù),用來(lái)做主線成檢查的挠蛉。

優(yōu)化啟動(dòng)時(shí)間

啟動(dòng)時(shí)間這個(gè)名詞祭示,不同的人有不同的定義。在我看來(lái)谴古,

啟動(dòng)時(shí)間是用戶點(diǎn)擊App圖標(biāo)质涛,到第一個(gè)界面展示的時(shí)間。

以main函數(shù)作為分水嶺掰担,啟動(dòng)時(shí)間其實(shí)包括了兩部分:main函數(shù)之前和main函數(shù)到第一個(gè)界面的viewDidAppear:汇陆。所以,優(yōu)化也是從兩個(gè)方面進(jìn)行的带饱,個(gè)人建議優(yōu)先優(yōu)化后者毡代,因?yàn)榻^大多數(shù)App的瓶頸在自己的代碼里阅羹。

Main函數(shù)之后

我們首先來(lái)分析下,從main函數(shù)開(kāi)始執(zhí)行教寂,到你的第一個(gè)界面顯示捏鱼,這期間一般會(huì)做哪些事情。

  • 執(zhí)行AppDelegate的代理方法酪耕,主要是didFinishLaunchingWithOptions
  • 初始化Window导梆,初始化基礎(chǔ)的ViewController結(jié)構(gòu)(一般是UINavigationController+UITabViewController)
  • 獲取數(shù)據(jù)(Local DB/Network),展示給用戶迂烁。

UIViewController

延遲初始化那些不必要的UIViewController看尼。</pre>

比如網(wǎng)易新聞:

image

在啟動(dòng)的時(shí)候只需要初始化首頁(yè)的頭條頁(yè)面即可。像“要聞”盟步,“我的”等頁(yè)面藏斩,則延遲加載,即啟動(dòng)的時(shí)候只是一個(gè)UIViewController作為占位符給TabController却盘,等到用戶點(diǎn)擊了再去進(jìn)行真正的數(shù)據(jù)和視圖的初始化工作狰域。

AppDelegate

通常我們會(huì)在AppDelegate的代理方法里進(jìn)行初始化工作,主要包括了兩個(gè)方法:

  • didFinishLaunchingWithOptions
  • applicationDidBecomeActive

優(yōu)化這些初始化的核心思想就是:

能延遲初始化的盡量延遲初始化谷炸,不能延遲初始化的盡量放到后臺(tái)初始化北专。

這些工作主要可以分為幾類:

  • 三方SDK初始化,比如Crash統(tǒng)計(jì); 像分享之類的旬陡,可以等到第一次調(diào)用再出初始化拓颓。
  • 初始化某些基礎(chǔ)服務(wù),比如WatchDog描孟,遠(yuǎn)程參數(shù)驶睦。
  • 啟動(dòng)相關(guān)日志,日志往往涉及到DB操作匿醒,一定要放到后臺(tái)去做
  • 業(yè)務(wù)方初始化场航,這個(gè)交由每個(gè)業(yè)務(wù)自己去控制初始化時(shí)間。

對(duì)于didFinishLaunchingWithOptions的代碼廉羔,建議按照以下的方式進(jìn)行劃分:

@interface AppDelegate ()
//業(yè)務(wù)方需要的生命周期回調(diào)
@property (strong, nonatomic) NSArray<id<UIApplicationDelegate>> * eventQueues;
//主框架負(fù)責(zé)的生命周期回調(diào)
@property (strong, nonatomic) id<UIApplicationDelegate> basicDelegate;
@end

然后溉痢,你會(huì)得到一個(gè)非常干凈的AppDelegate文件:

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    for (id<UIApplicationDelegate> delegate in self.eventQueues) {
    [delegate application:application didFinishLaunchingWithOptions:launchOptions];
    }
    return [self.basicDelegate application:application didFinishLaunchingWithOptions:launchOptions];
    }

由于對(duì)這些初始化進(jìn)行了分組,在開(kāi)發(fā)期就可以很容易的控制每一個(gè)業(yè)務(wù)的初始化時(shí)間:

CFTimeInterval startTime = CACurrentMediaTime();
//執(zhí)行方法
CFTimeInterval endTime = CACurrentMediaTime();</pre>

用Time Profiler找到元兇

Time Profiler在分析時(shí)間占用上非常強(qiáng)大憋他。實(shí)用的時(shí)候注意三點(diǎn)

  • 在打包模式下分析(一般是Release),這樣和線上環(huán)境一樣孩饼。
  • 記得開(kāi)啟dsym,不然無(wú)法查看到具體的函數(shù)調(diào)用堆棧
  • 分析性能差的設(shè)備竹挡,對(duì)于支持iOS 8的镀娶,一般分析iphone 4s或者iphone 5。

一個(gè)典型的分析界面如下:

image

幾點(diǎn)要注意:

  1. 分析啟動(dòng)時(shí)間揪罕,一般只關(guān)心主線程
  2. 選擇Hide System Libraries和Invert Call Tree梯码,這樣我們能專注于自己的代碼
  3. 右側(cè)可以看到詳細(xì)的調(diào)用堆棧信息

在某一行上雙擊宝泵,我們可以進(jìn)入到代碼預(yù)覽界面,去看看實(shí)際每一行占用了多少時(shí)間:

image

小結(jié)

不同的App在啟動(dòng)的時(shí)候做的事情往往不同轩娶,但是優(yōu)化起來(lái)的核心思想無(wú)非就兩個(gè):

  • 能延遲執(zhí)行的就延遲執(zhí)行儿奶。比如SDK的初始化,界面的創(chuàng)建鳄抒。
  • 不能延遲執(zhí)行的廓握,盡量放到后臺(tái)執(zhí)行。比如數(shù)據(jù)讀取嘁酿,原始JSON數(shù)據(jù)轉(zhuǎn)對(duì)象,日志發(fā)送男应。

Main函數(shù)之前

Main函數(shù)之前是iOS系統(tǒng)的工作闹司,所以這部分的優(yōu)化往往更具有通用性。

dylibs

啟動(dòng)的第一步是加載動(dòng)態(tài)庫(kù)沐飘,加載系統(tǒng)的動(dòng)態(tài)庫(kù)使很快的游桩,因?yàn)榭梢跃彺妫虞d內(nèi)嵌的動(dòng)態(tài)庫(kù)速度較慢耐朴。所以借卧,提高這一步的效率的關(guān)鍵是:減少動(dòng)態(tài)庫(kù)的數(shù)量。

合并動(dòng)態(tài)庫(kù)筛峭,比如公司內(nèi)部由私有Pod建立了如下動(dòng)態(tài)庫(kù):XXTableView, XXHUD, XXLabel铐刘,強(qiáng)烈建議合并成一個(gè)XXUIKit來(lái)提高加載速度。

Rebase & Bind & Objective C Runtime

Rebase和Bind都是為了解決指針引用的問(wèn)題影晓。對(duì)于Objective C開(kāi)發(fā)來(lái)說(shuō)镰吵,主要的時(shí)間消耗在Class/Method的符號(hào)加載上,所以常見(jiàn)的優(yōu)化方案是:

  • 減少__DATA段中的指針數(shù)量挂签。
  • 合并Category和功能類似的類疤祭。比如:UIView+Frame,UIView+AutoLayout…合并為一個(gè)
  • 刪除無(wú)用的方法和類。
  • 多用Swift Structs饵婆,因?yàn)镾wfit Structs是靜態(tài)分發(fā)的勺馆。感興趣的同學(xué)可以看看我之前這篇文章:《Swift進(jìn)階之內(nèi)存模型和方法調(diào)度》
  • Initializers

通常,我們會(huì)在+load方法中進(jìn)行method-swizzling侨核,這也是Nshipster推薦的方式草穆。

  • 用initialize替代load。不少同學(xué)喜歡用method-swizzling來(lái)實(shí)現(xiàn)AOP去做日志統(tǒng)計(jì)等內(nèi)容芹关,強(qiáng)烈建議改為在initialize進(jìn)行初始化续挟。
  • 減少atribute((constructor))的使用,而是在第一次訪問(wèn)的時(shí)候才用dispatch_once等方式初始化侥衬。
  • 不要?jiǎng)?chuàng)建線程
  • 使用Swfit重寫代碼诗祸。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跑芳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子直颅,更是在濱河造成了極大的恐慌博个,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件功偿,死亡現(xiàn)場(chǎng)離奇詭異盆佣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)械荷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門共耍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吨瞎,你說(shuō)我怎么就攤上這事痹兜。” “怎么了颤诀?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵字旭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我崖叫,道長(zhǎng)遗淳,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任心傀,我火速辦了婚禮屈暗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脂男。我一直安慰自己恐锦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布疆液。 她就那樣靜靜地躺著一铅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪堕油。 梳的紋絲不亂的頭發(fā)上潘飘,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音掉缺,去河邊找鬼卜录。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眶明,可吹牛的內(nèi)容都是我干的艰毒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼搜囱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丑瞧!你這毒婦竟也來(lái)了柑土?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绊汹,失蹤者是張志新(化名)和其女友劉穎稽屏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體西乖,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狐榔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了获雕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薄腻。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖届案,靈堂內(nèi)的尸體忽然破棺而出被廓,到底是詐尸還是另有隱情,我是刑警寧澤萝玷,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站昆婿,受9級(jí)特大地震影響球碉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仓蛆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一睁冬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧看疙,春花似錦豆拨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至搁胆,卻和暖如春弥搞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渠旁。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工攀例, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顾腊。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓粤铭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親杂靶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梆惯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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