如何實(shí)現(xiàn) iOS App 的冷啟動優(yōu)化

歡迎訪問我的博客原文

當(dāng) App 中的業(yè)務(wù)模塊越來越多、越來越復(fù)雜汰现,集成了更多的三方庫,App 啟動也會越來越慢叔壤,因此我們希望能在業(yè)務(wù)擴(kuò)張的同時瞎饲,保持較優(yōu)的啟動速度,給用戶帶來良好的使用體驗(yàn)炼绘。

熱啟動與冷啟動

當(dāng)用戶按下 home 鍵嗅战,iOS App 不會立刻被 kill,而是存活一段時間俺亮,這段時間里用戶再打開 App仗哨,App 基本上不需要做什么,就能還原到退到后臺前的狀態(tài)铅辞。我們把 App 進(jìn)程還在系統(tǒng)中厌漂,無需開啟新進(jìn)程的啟動過程稱為熱啟動

冷啟動則是指 App 不在系統(tǒng)進(jìn)程中斟珊,比如設(shè)備重啟后苇倡,或是手動殺死 App 進(jìn)程富纸,又或是 App 長時間未打開過,用戶再點(diǎn)擊啟動 App 的過程旨椒,這時需要創(chuàng)建一個新進(jìn)程分配給 App晓褪。我們可以將冷啟動看作一次完整的 App 啟動過程,本文討論的就是冷啟動的優(yōu)化综慎。

冷啟動概要

WWDC 2016 中首次出現(xiàn)了 App 啟動優(yōu)化的話題涣仿,其中提到:

  • App 啟動最佳速度是400ms以內(nèi),因?yàn)閺狞c(diǎn)擊 App 圖標(biāo)啟動示惊,然后 Launch Screen 出現(xiàn)再消失的時間就是400ms好港;
  • App 啟動最慢不得大于20s,否則進(jìn)程會被系統(tǒng)殺死米罚;(啟動時間最好以 App 所支持的最低配置設(shè)備為準(zhǔn)钧汹。)

冷啟動的整個過程是指從用戶喚起 App 開始到 AppDelegate 中的 didFinishLaunchingWithOptions 方法執(zhí)行完畢為止,并以執(zhí)行 main() 函數(shù)的時機(jī)為分界點(diǎn)录择,分為 pre-mainmain() 兩個階段拔莱。

也有一種說法是將整個冷啟動階段以主 UI 框架的 viewDidAppear 函數(shù)執(zhí)行完畢才算結(jié)束。這兩種說法都可以隘竭,前者的界定范圍是 App 啟動和初始化完畢塘秦,后者的界定范圍是用戶視角的啟動完畢,也就是首屏已經(jīng)被加載出來动看。

注意:這里很多文章都會把第二個階段描述為 main 函數(shù)之后尊剔,個人認(rèn)為這種說法不是很好,容易讓人誤解弧圆。要知道 main 函數(shù)在 App 運(yùn)行過程中是不會退出的赋兵,無論是 AppDelegate 中的 didFinishLaunchingWithOptions 方法還是 ViewController 中的viewDidAppear 方法笔咽,都還是在 main 函數(shù)內(nèi)部執(zhí)行的搔预。

pre-main 階段

pre-main 階段指的是從用戶喚起 App 到 main() 函數(shù)執(zhí)行之前的過程。

查看階段耗時

我們可以在 Xcode 中配置環(huán)境變量 DYLD_PRINT_STATISTICS 為 1(Edit Scheme → Run → Arguments → Environment Variables → +)叶组。

設(shè)置環(huán)境變量

這時在 iOS 10 以上系統(tǒng)中運(yùn)行一個 TestDemo拯田,pre-main 階段的啟動時間會在控制臺中打印出來。

Total pre-main time: 354.21 milliseconds (100.0%)
         dylib loading time:  25.52 milliseconds (7.2%)
        rebase/binding time:  12.70 milliseconds (3.5%)
            ObjC setup time: 152.74 milliseconds (43.1%)
           initializer time: 163.24 milliseconds (46.0%)
           slowest intializers :
             libSystem.B.dylib :   7.98 milliseconds (2.2%)
   libBacktraceRecording.dylib :  13.53 milliseconds (3.8%)
    libMainThreadChecker.dylib :  41.11 milliseconds (11.6%)
                      TestDemo :  88.76 milliseconds (25.0%)

如果要更詳細(xì)的信息甩十,就設(shè)置 DYLD_PRINT_STATISTICS_DETAILS 為 1船庇。

  total time: 1.6 seconds (100.0%)
  total images loaded:  388 (381 from dyld shared cache)
  total segments mapped: 23, into 413 pages
  total images loading time: 805.78 milliseconds (48.6%)
  total load time in ObjC: 152.74 milliseconds (9.2%)
  total debugger pause time: 780.26 milliseconds (47.1%)
  total dtrace DOF registration time:   0.00 milliseconds (0.0%)
  total rebase fixups:  54,265
  total rebase fixups time:  20.77 milliseconds (1.2%)
  total binding fixups: 527,211
  total binding fixups time: 513.54 milliseconds (31.0%)
  total weak binding fixups time:   0.31 milliseconds (0.0%)
  total redo shared cached bindings time: 521.93 milliseconds (31.5%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load: 163.24 milliseconds (9.8%)
                         libSystem.B.dylib :   7.98 milliseconds (0.4%)
               libBacktraceRecording.dylib :  13.53 milliseconds (0.8%)
                libMainThreadChecker.dylib :  41.11 milliseconds (2.4%)
              libViewDebuggerSupport.dylib :   6.68 milliseconds (0.4%)
                                  TestDemo :  88.76 milliseconds (5.3%)
total symbol trie searches:    1306942
total symbol table binary searches:    0
total images defining weak symbols:  41
total images using weak symbols:  105

這里統(tǒng)計(jì)到的啟動耗時出現(xiàn)一定波動是正常的,無須過分在意侣监。

理論知識

為了更準(zhǔn)確地了解 App 啟動的流程鸭轮,我們先熟悉一下幾個概念。

Mach-O

Mach-O(Mach Object File Format)是一種用于記錄可執(zhí)行文件橄霉、對象代碼窃爷、共享庫、動態(tài)加載代碼和內(nèi)存轉(zhuǎn)儲的文件格式。App 編譯生成的二進(jìn)制可執(zhí)行文件就是 Mach-O 格式的按厘,iOS 工程所有的類編譯后會生成對應(yīng)的目標(biāo)文件 .o 文件医吊,而這個可執(zhí)行文件就是這些 .o 文件的集合。

在 Xcode 的控制臺輸入以下命令逮京,可以打印出運(yùn)行時所有加載進(jìn)應(yīng)用程序的 Mach-O 文件约巷。

image list -o -f

Mach-O 文件主要由三部分組成:

  • Mach header:描述 Mach-O 的 CPU 架構(gòu)弟塞、文件類型以及加載命令等;
  • Load commands:描述了文件中數(shù)據(jù)的具體組織結(jié)構(gòu),不同的數(shù)據(jù)類型使用不同的加載命令反粥;
  • Data:Data 中的每個段(segment)的數(shù)據(jù)都保存在這里,每個段都有一個或多個 Section茸习,它們存放了具體的數(shù)據(jù)與代碼柔逼,主要包含這三種類型:
    • __TEXT 包含 Mach header,被執(zhí)行的代碼和只讀常量(如C 字符串)享钞。只讀可執(zhí)行(r-x)揍诽。
    • __DATA 包含全局變量,靜態(tài)變量等栗竖∈畲啵可讀寫(rw-)。
    • __LINKEDIT 包含了加載程序的元數(shù)據(jù)狐肢,比如函數(shù)的名稱和地址添吗。只讀(r–-)。

dylib

dylib 也是一種 Mach-O 格式的文件份名,后綴名為 .dylib 的文件就是動態(tài)庫(也叫動態(tài)鏈接庫)碟联。動態(tài)庫是運(yùn)行時加載的,可以被多個 App 的進(jìn)程共用僵腺。

如果想知道 TestDemo 中依賴的所有動態(tài)庫鲤孵,可以通過下面的指令實(shí)現(xiàn):

otool -L /TestDemo.app/TestDemo

動態(tài)鏈接庫分為系統(tǒng) dylib內(nèi)嵌 dylib(embed dylib,即開發(fā)者手動引入的動態(tài)庫)辰如。系統(tǒng) dylib 有:

  • iOS 中用到的所有系統(tǒng) framework普监,比如 UIKit、Foundation琉兜;
  • 系統(tǒng)級別的 libSystem(如 libdispatch(GCD) 和 libsystem_blocks(Block))凯正;
  • 加載 OC runtime 方法的 libobjc;
  • ……

dyld

dyld(Dynamic Link Editor):動態(tài)鏈接器豌蟋,其本質(zhì)也是 Mach-O 文件廊散,一個專門用來加載 dylib 文件的庫。 dyld 位于 /usr/lib/dyld梧疲,可以在 mac 和越獄機(jī)中找到允睹。dyld 會將 App 依賴的動態(tài)庫和 App 文件加載到內(nèi)存后執(zhí)行施符。

dyld shared cache

dyld shared cache 就是動態(tài)庫共享緩存。當(dāng)需要加載的動態(tài)庫非常多時擂找,相互依賴的符號也更多了戳吝,為了節(jié)省解析處理符號的時間,OS X 和 iOS 上的動態(tài)鏈接器使用了共享緩存贯涎。OS X 的共享緩存位于 /private/var/db/dyld/听哭,iOS 的則在 /System/Library/Caches/com.apple.dyld/

當(dāng)加載一個 Mach-O 文件時塘雳,dyld 首先會檢查是否存在于共享緩存陆盘,存在就直接取出使用。每一個進(jìn)程都會把這個共享緩存映射到了自己的地址空間中败明。這種方法大大優(yōu)化了 OS X 和 iOS 上程序的啟動時間隘马。

images

images 在這里不是指圖片,而是鏡像妻顶。每個 App 都是以 images 為單位進(jìn)行加載的酸员。images 類型包括:

  • executable:應(yīng)用的二進(jìn)制可執(zhí)行文件;
  • dylib:動態(tài)鏈接庫讳嘱;
  • bundle:資源文件幔嗦,屬于不能被鏈接的 dylib,只能在運(yùn)行時通過 dlopen() 加載沥潭。

framework

framework 可以是動態(tài)庫邀泉,也是靜態(tài)庫,是一個包含 dylib钝鸽、bundle 和頭文件的文件夾汇恤。

啟動過程分析與優(yōu)化

啟動一個應(yīng)用時,系統(tǒng)會通過 fork() 方法來新創(chuàng)建一個進(jìn)程拔恰,然后執(zhí)行鏡像通過 exec() 來替換為另一個可執(zhí)行程序因谎,然后執(zhí)行如下操作:

  1. 把可執(zhí)行文件加載到內(nèi)存空間,從可執(zhí)行文件中能夠分析出 dyld 的路徑仁连;
  2. 把 dyld 加載到內(nèi)存蓝角;
  3. dyld 從可執(zhí)行文件的依賴開始阱穗,遞歸加載所有的依賴動態(tài)鏈接庫 dylib 并進(jìn)行相應(yīng)的初始化操作饭冬。

結(jié)合上面 pre-main 打印的結(jié)果,我們可以大致了解整個啟動過程如下圖所示:

pre-main 階段啟動過程

Load Dylibs

這一步揪阶,指的是動態(tài)庫加載昌抠。在此階段,dyld 會:

  1. 分析 App 依賴的所有 dylib鲁僚;
  2. 找到 dylib 對應(yīng)的 Mach-O 文件炊苫;
  3. 打開裁厅、讀取這些 Mach-O 文件,并驗(yàn)證其有效性侨艾;
  4. 在系統(tǒng)內(nèi)核中注冊代碼簽名执虹;
  5. 對 dylib 的每一個 segment 調(diào)用 mmap()

一般情況下唠梨,iOS App 需要加載 100-400 個 dylibs袋励。這些動態(tài)庫包括系統(tǒng)的,也包括開發(fā)者手動引入的当叭。其中大部分 dylib 都是系統(tǒng)庫茬故,系統(tǒng)已經(jīng)做了優(yōu)化,因此開發(fā)者更應(yīng)關(guān)心自己手動集成的內(nèi)嵌 dylib蚁鳖,加載它們時性能開銷較大磺芭。

App 中依賴的 dylib 越少越好,Apple 官方建議盡量將內(nèi)嵌 dylib 的個數(shù)維持在6個以內(nèi)醉箕。

優(yōu)化方案

  • 盡量不使用內(nèi)嵌 dylib钾腺;
  • 合并已有內(nèi)嵌 dylib;
  • 檢查 framework 的 optionalrequired 設(shè)置讥裤,如果 framework 在當(dāng)前的 App 支持的 iOS 系統(tǒng)版本中都存在垮庐,就設(shè)為 required,因?yàn)樵O(shè)為 optional 會有額外的檢查坞琴;
  • 使用靜態(tài)庫作為代替哨查;(不過靜態(tài)庫會在編譯期被打進(jìn)可執(zhí)行文件,造成可執(zhí)行文件體積增大剧辐,兩者各有利弊寒亥,開發(fā)者自行權(quán)衡。)
  • 懶加載 dylib荧关。(但使用 dlopen() 對性能會產(chǎn)生影響溉奕,因?yàn)?App 啟動時是原本是單線程運(yùn)行,系統(tǒng)會取消加鎖忍啤,但 dlopen() 開啟了多線程加勤,系統(tǒng)不得不加鎖,這樣不僅會使性能降低同波,可能還會造成死鎖及未知的后果鳄梅,不是很推薦這種做法。)

Rebase/Binding

這一步未檩,做的是指針重定位戴尸。

在 dylib 的加載過程中,系統(tǒng)為了安全考慮冤狡,引入了 ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名孙蒙。由于 ASLR 的存在项棠,鏡像會在新的隨機(jī)地址(actual_address)上加載,和之前指針指向的地址(preferred_address)會有一個偏差(slide挎峦,slide=actual_address-preferred_address)香追,因此 dyld 需要修正這個偏差,指向正確的地址坦胶。具體通過這兩步實(shí)現(xiàn):

第一步:Rebase翅阵,在 image 內(nèi)部調(diào)整指針的指向。將 image 讀入內(nèi)存迁央,并以 page 為單位進(jìn)行加密驗(yàn)證掷匠,保證不會被篡改,性能消耗主要在 IO岖圈。

第二步:Binding讹语,符號綁定。將指針指向 image 外部的內(nèi)容蜂科。查詢符號表顽决,設(shè)置指向鏡像外部的指針,性能消耗主要在 CPU 計(jì)算导匣。

通過以下命令可以查看 rebase 和 bind 等信息:

xcrun dyldinfo -rebase -bind -lazy_bind TestDemo.app/TestDemo

通過 LC_DYLD_INFO_ONLY 可以查看各種信息的偏移量和大小才菠。如果想要更方便直觀地查看,推薦使用 MachOView 工具贡定。

指針數(shù)量越少赋访,指針修復(fù)的耗時也就越少。所以缓待,優(yōu)化該階段的關(guān)鍵就是減少 __DATA 段中的指針數(shù)量蚓耽。

優(yōu)化方案

  • 減少 ObjC 類(class)、方法(selector)旋炒、分類(category)的數(shù)量步悠,比如合并一些功能,刪除無效的類瘫镇、方法和分類等(可以借助 AppCode 的 Inspect Code 功能進(jìn)行代碼瘦身)鼎兽;
  • 減少 C++ 虛函數(shù);(虛函數(shù)會創(chuàng)建 vtable铣除,這也會在 __DATA 段中創(chuàng)建結(jié)構(gòu)谚咬。)
  • 多用 Swift Structs。(因?yàn)?Swift Structs 是靜態(tài)分發(fā)的通孽,它的結(jié)構(gòu)內(nèi)部做了優(yōu)化序宦,符號數(shù)量更少睁壁。)

ObjC Setup

完成 Rebase 和 Bind 之后背苦,通知 runtime 去做一些代碼運(yùn)行時需要做的事情:

  • dyld 會注冊所有聲明過的 ObjC 類互捌;
  • 將分類插入到類的方法列表中;
  • 檢查每個 selector 的唯一性行剂。

優(yōu)化方案

Rebase/Binding 階段優(yōu)化好了秕噪,這一步的耗時也會相應(yīng)減少。

Initializers

Rebase 和 Binding 屬于靜態(tài)調(diào)整(fix-up)厚宰,修改的是 __DATA 段中的內(nèi)容腌巾,而這里則開始動態(tài)調(diào)整,往堆和棧中寫入內(nèi)容铲觉。具體工作有:

  • 調(diào)用每個 Objc 類和分類中的 +load 方法澈蝙;
  • 調(diào)用 C/C++ 中的構(gòu)造器函數(shù)(用 attribute((constructor)) 修飾的函數(shù));
  • 創(chuàng)建非基本類型的 C++ 靜態(tài)全局變量撵幽。

優(yōu)化方案

  • 盡量避免在類的 +load 方法中初始化灯荧,可以推遲到 +initiailize 中進(jìn)行;(因?yàn)樵谝粋€ +load 方法中進(jìn)行運(yùn)行時方法替換操作會帶來 4ms 的消耗)
  • 避免使用 __atribute__((constructor)) 將方法顯式標(biāo)記為初始化器盐杂,而是讓初始化方法調(diào)用時再執(zhí)行逗载。比如用 dispatch_once()pthread_once()std::once()链烈,相當(dāng)于在第一次使用時才初始化厉斟,推遲了一部分工作耗時。:
  • 減少非基本類型的 C++ 靜態(tài)全局變量的個數(shù)强衡。(因?yàn)檫@類全局變量通常是類或者結(jié)構(gòu)體擦秽,如果在構(gòu)造函數(shù)中有繁重的工作,就會拖慢啟動速度)

總結(jié)一下 pre-main 階段可行的優(yōu)化方案:

  • 重新梳理架構(gòu)漩勤,減少不必要的內(nèi)置動態(tài)庫數(shù)量
  • 進(jìn)行代碼瘦身号涯,合并或刪除無效的ObjC類、Category锯七、方法链快、C++ 靜態(tài)全局變量等
  • 將不必須在 +load 方法中執(zhí)行的任務(wù)延遲到 +initialize
  • 減少 C++ 虛函數(shù)

main() 階段

對于 main() 階段,主要測量的就是從 main() 函數(shù)開始執(zhí)行到 didFinishLaunchingWithOptions 方法執(zhí)行結(jié)束的耗時眉尸。

查看階段耗時

這里介紹兩種查看 main() 階段耗時的方法域蜗。

方法一:手動插入代碼,進(jìn)行耗時計(jì)算噪猾。

// 第一步:在 main() 函數(shù)里用變量 MainStartTime 記錄當(dāng)前時間
CFAbsoluteTime MainStartTime;
int main(int argc, char * argv[]) {
    MainStartTime = CFAbsoluteTimeGetCurrent();
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

// 第二步:在 AppDelegate.m 文件中用 extern 聲明全局變量 MainStartTime
extern CFAbsoluteTime MainStartTime;

// 第三步:在 didFinishLaunchingWithOptions 方法結(jié)束前霉祸,再獲取一下當(dāng)前時間,與 MainStartTime 的差值就是 main() 函數(shù)階段的耗時
double mainLaunchTime = (CFAbsoluteTimeGetCurrent() - MainStartTime);
NSLog(@"main() 階段耗時:%.2fms", mainLaunchTime * 1000);

方法二:借助 Instruments 的 Time Profiler 工具查看耗時袱蜡。

打開方式為:Xcode → Open Developer Tool → Instruments → Time Profiler丝蹭。

Time Profiler

操作步驟:

  1. 配置 Scheme。點(diǎn)擊 Edit Scheme 找到 Profile 下的 Build Configuration坪蚁,設(shè)置為 Debug奔穿。

  2. 配置 PROJECT镜沽。點(diǎn)擊 PROJECT,在 Build Settings 中找到 Build Options 選項(xiàng)里的 Debug Information Format贱田,把 Debug 對應(yīng)的值改為 DWARF with dSYM File缅茉。

  3. 啟動 Time Profiler,點(diǎn)擊左上角紅色圓形按鈕開始檢測男摧,然后就可以看到執(zhí)行代碼的完整路徑和對應(yīng)的耗時蔬墩。

為了方面查看應(yīng)用程序中實(shí)際代碼的執(zhí)行耗時和代碼路徑實(shí)際所在的位置,可以勾選上 Call Tree 中的 Separate ThreadHide System Libraries耗拓。

查看啟動耗時

啟動優(yōu)化

main() 被調(diào)用之后拇颅,didFinishLaunchingWithOptions 階段,App 會進(jìn)行必要的初始化操作乔询,而 viewDidAppear 執(zhí)行結(jié)束之前則是做了首頁內(nèi)容的加載和顯示蔬蕊。

關(guān)于 App 的初始化,除了統(tǒng)計(jì)哥谷、日志這種須要在 App 一啟動就配置的事件岸夯,有一些配置也可以考慮延遲加載。如果你在 didFinishLaunchingWithOptions 中同時也涉及到了首屏的加載们妥,那么可以考慮從這些角度優(yōu)化:

  • 用純代碼的方式猜扮,而不是 xib/Storyboard,來加載首頁視圖
  • 延遲暫時不需要的二方/三方庫加載监婶;
  • 延遲執(zhí)行部分業(yè)務(wù)邏輯和 UI 配置旅赢;
  • 延遲加載/懶加載部分視圖;
  • 避免首屏加載時大量的本地/網(wǎng)絡(luò)數(shù)據(jù)讀然蠡獭煮盼;
  • 在 release 包中移除 NSLog 打印带污;
  • 在視覺可接受的范圍內(nèi)僵控,壓縮頁面中的圖片大小鱼冀;
  • ……

如果首屏為 H5 頁面报破,針對它的優(yōu)化,參考 VasSonic 的原理千绪,可以從這幾個角度入手:

  • 終端耗時

    • webView 預(yù)加載:在 App 啟動時期預(yù)先加載了一次 webView充易,通過創(chuàng)建空的 webView,預(yù)先啟動 Web 線程荸型,完成一些全局性的初始化工作盹靴,對二次創(chuàng)建 webView 能有數(shù)百毫秒的提升。
  • 頁面耗時(靜態(tài)頁面)

    • 靜態(tài)直出:服務(wù)端拉取數(shù)據(jù)后通過 Node.js 進(jìn)行渲染,生成包含首屏數(shù)據(jù)的 HTML 文件稿静,發(fā)布到 CDN 上梭冠,webView 直接從 CDN 上獲取自赔;
    • 離線預(yù)推:使用離線包妈嘹。
  • 頁面耗時(經(jīng)常需要動態(tài)更新的頁面)

    • 并行加載:WebView 的打開和資源的請求并行柳琢;
    • 動態(tài)緩存:動態(tài)頁面緩存在客戶端绍妨,用戶下次打開的時候先打開緩存頁面,然后再刷新柬脸;
    • 動靜分離:將頁面分為靜態(tài)模板和動態(tài)數(shù)據(jù)他去,根據(jù)不同的啟動場景進(jìn)行不同的刷新方案;
    • 預(yù)加載:提前拉取需要的增量更新數(shù)據(jù)倒堕。

小結(jié)

隨著業(yè)務(wù)的增長灾测,App 中的模塊越來越多,冷啟動的時間也必不可少地增加垦巴。冷啟動本就是一個比較復(fù)雜的流程媳搪,它的優(yōu)化沒有固定的公式,我們需要結(jié)合業(yè)務(wù)骤宣,配合一些性能分析工具和線上監(jiān)控日志秦爆,有耐心、多維度地進(jìn)行分析和解決憔披。


參考鏈接:

WWDC2016: Optimizing App Startup Time
WWDC2017: App Startup Time: Past, Present, and Future
優(yōu)化 App 的啟動時間
今日頭條 iOS 客戶端啟動速度優(yōu)化
VasSonic 源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末等限,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芬膝,更是在濱河造成了極大的恐慌望门,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锰霜,死亡現(xiàn)場離奇詭異筹误,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)癣缅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門纫事,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人所灸,你說我怎么就攤上這事丽惶。” “怎么了爬立?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵钾唬,是天一觀的道長。 經(jīng)常有香客問我,道長抡秆,這世上最難降的妖魔是什么奕巍? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮儒士,結(jié)果婚禮上的止,老公的妹妹穿的比我還像新娘。我一直安慰自己着撩,他們只是感情好诅福,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拖叙,像睡著了一般氓润。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上薯鳍,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天咖气,我揣著相機(jī)與錄音,去河邊找鬼挖滤。 笑死崩溪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斩松。 我是一名探鬼主播伶唯,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼砸民!你這毒婦竟也來了抵怎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤岭参,失蹤者是張志新(化名)和其女友劉穎反惕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體演侯,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姿染,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了秒际。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悬赏。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娄徊,靈堂內(nèi)的尸體忽然破棺而出闽颇,到底是詐尸還是另有隱情,我是刑警寧澤寄锐,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布兵多,位于F島的核電站尖啡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剩膘。R本人自食惡果不足惜衅斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怠褐。 院中可真熱鬧畏梆,春花似錦、人聲如沸奈懒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筐赔。三九已至铣猩,卻和暖如春揖铜,著一層夾襖步出監(jiān)牢的瞬間茴丰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工天吓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贿肩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓龄寞,卻偏偏與公主長得像汰规,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子物邑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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

  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記溜哮,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動時間。...
    MTDeveloper閱讀 748評論 0 1
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記色解,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動時間茂嗓。...
    茗涙閱讀 1,860評論 0 3
  • 本文分為理論【1-4】和實(shí)踐【5-6】兩部分: main()函數(shù)之前發(fā)生了什么 Mach-O格式 虛擬內(nèi)存基礎(chǔ)知識...
    WSJay閱讀 912評論 0 1
  • 探究App的啟動過程,有助于我們優(yōu)化App的啟動時間科阎,從main函數(shù)之前和main函數(shù)之后兩個階段進(jìn)行分析一下述吸。 ...
    沉江小魚閱讀 1,469評論 1 6
  • (有覺察的吵架) 昨天睡覺的時候,跟玖一直在溝通他獨(dú)立在高低床上面睡覺的事情(我和爸爸還是睡下面陪他)锣笨。 他勉強(qiáng)同...
    99媽閱讀 104評論 0 0