1.mach-0文件的結(jié)構(gòu)都有哪些陕见,iOS程序main()函數(shù)之前和之后分別都做了些什么浪读?
-
Mach-O文件格式是OS X與iOS系統(tǒng)上的可執(zhí)行文件格式,像我們編譯過程產(chǎn)生的.O文件,以及程序的可執(zhí)行文件,動態(tài)庫等都是Mach-O文件辅搬,它的結(jié)構(gòu)如下:
Header
: 保存了一些基本信息,包括了該文件運行的平臺脖旱、文件類型堪遂、LoadCommands的個數(shù)等。
LoadCommands
: 可以理解為加載命令萌庆,在加載Mach-O文件時會使用這里的數(shù)據(jù)來確定內(nèi)存的分布以及相關(guān)的加載命令溶褪。比如我們的main函數(shù)的加載地址,程序所需的dyld的文件路徑踊兜,以及相關(guān)依賴庫的文件路徑竿滨。
Data
:這里包含了具體的代碼佳恬、數(shù)據(jù)等捏境。
- t(App總啟動時間) = t1(main()之前的加載時間) + t2(main()之后的加載時間),t1 = 系統(tǒng)dylib(動態(tài)鏈接庫)和自身App可執(zhí)行文件的加載;
t2 = main方法執(zhí)行之后到AppDelegate類中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行結(jié)束前這段時間毁葱,主要是構(gòu)建第一個界面垫言,并完成渲染展示。
App開始啟動后倾剿,系統(tǒng)首先加載可執(zhí)行文件(自身App的所有.o文件的集合)筷频,然后加載動態(tài)鏈接庫dyld,dyld是一個專門用來加載動態(tài)鏈接庫的庫前痘。 執(zhí)行從dyld開始凛捏,dyld從可執(zhí)行文件的依賴開始, 遞歸加載所有的依賴動態(tài)鏈接庫。
動態(tài)鏈接庫包括:iOS 中用到的所有系統(tǒng) framework芹缔,加載OC runtime方法的libobjc坯癣,系統(tǒng)級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)最欠。
無論對于系統(tǒng)的動態(tài)鏈接庫還是對于App本身的可執(zhí)行文件而言示罗,他們都算是image(鏡像),而每個App都是以image(鏡像)為單位進(jìn)行加載的芝硬,那么image究竟包括哪些呢蚜点?
1.executable可執(zhí)行文件 比如.o文件。
2.dylib 動態(tài)鏈接庫 framework就是動態(tài)鏈接庫和相應(yīng)資源包含在一起的一個文件夾結(jié)構(gòu)拌阴。
3.bundle 資源文件 只能用dlopen加載绍绘,不推薦使用這種方式加載。
所有動態(tài)鏈接庫和我們App中的靜態(tài)庫.a和所有類文件編譯后的.o文件最終都是由dyld(the dynamic link editor)Apple的動態(tài)鏈接器來加載到內(nèi)存中。每個image都是由一個叫做ImageLoader的類來負(fù)責(zé)加載(一一對應(yīng)).
- 動態(tài)鏈接庫加載的具體流程
動態(tài)鏈接庫的加載步驟具體分為5步:
1.load dylibs image 讀取庫鏡像文件
2.Rebase image
3.Bind image
4.Objc setup
5.initializers
load dylibs image
在每個動態(tài)庫的加載過程中陪拘, dyld需要:
分析所依賴的動態(tài)庫
找到動態(tài)庫的mach-o文件
打開文件
驗證文件
在系統(tǒng)核心注冊文件簽名
對動態(tài)庫的每一個segment調(diào)用mmap()
tips:
通常的实辑,一個App需要加載100到400個dylibs, 但是其中的系統(tǒng)庫被優(yōu)化藻丢,可以很快的加載剪撬。 針對這一步驟的優(yōu)化有:
1.減少非系統(tǒng)庫的依賴
2.合并非系統(tǒng)庫
3.使用靜態(tài)資源,比如把代碼加入主程序
rebase/bind
由于ASLR(address space layout randomization)的存在悠反,可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的加載地址每次啟動都不固定残黑,所以需要這2步來修復(fù)鏡像中的資源指針,來指向正確的地址斋否。 rebase修復(fù)的是指向當(dāng)前鏡像內(nèi)部的資源指針梨水; 而bind指向的是鏡像外部的資源指針。
rebase步驟先進(jìn)行茵臭,需要把鏡像讀入內(nèi)存疫诽,并以page為單位進(jìn)行加密驗證,保證不會被篡改旦委,所以這一步的瓶頸在IO奇徒。bind在其后進(jìn)行,由于要查詢符號表缨硝,來指向跨鏡像的資源摩钙,加上在rebase階段,鏡像已被讀入和加密驗證查辩,所以這一步的瓶頸在于CPU計算胖笛。
tips:
優(yōu)化該階段的關(guān)鍵在于減少__DATA segment中的指針數(shù)量。我們可以優(yōu)化的點有:
1.減少Objc類數(shù)量宜岛, 減少selector數(shù)量
2.減少C++虛函數(shù)數(shù)量
3.轉(zhuǎn)而使用swift stuct(其實本質(zhì)上就是為了減少符號的數(shù)量)
Objc setup
這一步主要工作是:
1.注冊O(shè)bjc類 (class registration)
2.把category的定義插入方法列表 (category registration)
3.保證每一個selector唯一 (selctor uniquing)
initializers
以上三步屬于靜態(tài)調(diào)整(fix-up)长踊,都是在修改__DATA segment中的內(nèi)容,而這里則開始動態(tài)調(diào)整萍倡,開始在堆和堆棧中寫入內(nèi)容身弊。 在這里的工作有:
1.Objc的+load()函數(shù)
2.C++的構(gòu)造函數(shù)屬性函數(shù) 形如attribute((constructor)) void DoSomeInitializationWork()
3.非基本類型的C++靜態(tài)全局變量的創(chuàng)建(通常是類或結(jié)構(gòu)體)(non-trivial initializer) 比如一個全局靜態(tài)結(jié)構(gòu)體的構(gòu)建,如果在構(gòu)造函數(shù)中有繁重的工作遣铝,那么會拖慢啟動速度
main()調(diào)用之前總結(jié):
對于main()調(diào)用之前的耗時我們可以優(yōu)化的點有:
1.減少不必要的framework佑刷,因為動態(tài)鏈接比較耗時
2.check framework應(yīng)當(dāng)設(shè)為optional和required,如果該framework在當(dāng)前App支持的所有iOS系統(tǒng)版本都存在酿炸,那么就設(shè)為required瘫絮,否則就設(shè)為optional,因為optional會有些額外的檢查
3.合并或者刪減一些OC類填硕,關(guān)于清理項目中沒用到的類麦萤,使用工具AppCode代碼檢查功能鹿鳖。
4.刪減一些無用的靜態(tài)變量
5.刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法
6.將不必須在+load方法中做的事情延遲到+initialize中
7.盡量不要用C++虛函數(shù)(創(chuàng)建虛函數(shù)表有開銷)
- main()調(diào)用之后的加載時間
在main()被調(diào)用之后,App的主要工作就是初始化必要的服務(wù)壮莹,顯示首頁內(nèi)容等翅帜。而我們的優(yōu)化也是圍繞如何能夠快速展現(xiàn)首頁來開展。 App通常在AppDelegate類中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中創(chuàng)建首頁需要展示的view命满,然后在當(dāng)前runloop的末尾涝滴,主動調(diào)用CA::Transaction::commit完成視圖的渲染。
而視圖的渲染主要涉及三個階段:
1.準(zhǔn)備階段 這里主要是圖片的解碼
2.布局階段 首頁所有UIView的- (void)layoutSubViews()運行
3.繪制階段 首頁所有UIView的- (void)drawRect:(CGRect)rect運行
再加上啟動之后必要服務(wù)的啟動胶台、必要數(shù)據(jù)的創(chuàng)建和讀取歼疮,這些就是我們可以嘗試優(yōu)化的地方
因此,對于main()函數(shù)調(diào)用之前我們可以優(yōu)化的點有:
1.不使用xib诈唬,直接視用代碼加載首頁視圖
2.NSUserDefaults實際上是在Library文件夾下會生產(chǎn)一個plist文件韩脏,如果文件太大的話一次能讀取到內(nèi)存中可能很耗時,這個影響需要評估铸磅,如果耗時很大的話需要拆分(需考慮老版本覆蓋安裝兼容問題)
3.每次用NSLog方式打印會隱式的創(chuàng)建一個Calendar赡矢,因此需要刪減啟動時各業(yè)務(wù)方打的log,或者僅僅針對內(nèi)測版輸出log
4.梳理應(yīng)用啟動時發(fā)送的所有網(wǎng)絡(luò)請求阅仔,是否可以統(tǒng)一在異步線程請求
- 針對app我們可以優(yōu)化的點如下:
1.純代碼方式而不是storyboard加載首頁UI吹散。
2.對didFinishLaunching里的函數(shù)考慮能否挖掘可以延遲加載或者懶加載,需要與各個業(yè)務(wù)方pm和rd共同check 對于一些已經(jīng)下線的業(yè)務(wù)霎槐,刪減冗余代碼送浊。
3.對于一些與UI展示無關(guān)的業(yè)務(wù),如微博認(rèn)證過期檢查丘跌、圖片最大緩存空間設(shè)置等做延遲加載
4.對實現(xiàn)了+load()方法的類進(jìn)行分析,盡量將load里的代碼延后調(diào)用唁桩。
5.上面統(tǒng)計數(shù)據(jù)顯示展示feed的導(dǎo)航控制器頁面(NewsListViewController)比較耗時闭树,對于viewDidLoad以及viewWillAppear方法中盡量去嘗試少做,晚做荒澡,不做报辱。
2.app哪些情況會產(chǎn)生崩潰
1.數(shù)組越界訪問
2.調(diào)用了未實現(xiàn)的方法
3.野指針
4.返回空cell
5.類釋放時未remove通知,之后收到通知
6.類釋放時delegate未置空单山,之后被回調(diào)
7.內(nèi)存暴漲
8.app升級改變了數(shù)據(jù)結(jié)構(gòu)
9.字符串的截取越界導(dǎo)致的崩潰
3.事件的傳遞和響應(yīng)機(jī)制
- 事件的產(chǎn)生
1.發(fā)生觸摸事件后碍现,系統(tǒng)會將該事件加入到一個由UIApplication管理的事件隊列中,為什么是隊列而不是棧?因為隊列的特點是FIFO米奸,即先進(jìn)先出昼接,先產(chǎn)生的事件先處理才符合常理,所以把事件添加到隊列悴晰。
2.UIApplication會從事件隊列中取出最前面的事件慢睡,并將事件分發(fā)下去以便處理逐工,通常,先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)漂辐。
主窗口會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件泪喊,這也是整個事件處理過程的第一步。
3.找到合適的視圖控件后髓涯,就會調(diào)用視圖控件的touches方法來作具體的事件處理袒啼。
- 事件的傳遞
1.觸摸事件的傳遞是從父控件傳遞到子控件
2.也就是UIApplication->window->尋找處理事件最合適的view
tips: 如果父控件不能接受觸摸事件,那么子控件就不可能接收到觸摸事件
事件的傳遞順序是這樣的:
產(chǎn)生觸摸事件->UIApplication事件隊列->[UIWindow hitTest:withEvent:]->返回更合適的view->[子控件 hitTest:withEvent:]->返回最合適的view
事件傳遞給窗口或控件的后纬纪,就調(diào)用hitTest:withEvent:方法尋找更合適的view瘤泪。所以是,先傳遞事件育八,再根據(jù)事件在自己身上找更合適的view对途。
不管子控件是不是最合適的view,系統(tǒng)默認(rèn)都要先把事件傳遞給子控件髓棋,經(jīng)過子控件調(diào)用子控件自己的hitTest:withEvent:方法驗證后才知道有沒有更合適的view实檀。即便父控件是最合適的view了,子控件的hitTest:withEvent:方法還是會調(diào)用按声,不然怎么知道有沒有更合適的膳犹!即,如果確定最終父控件是最合適的view签则,那么該父控件的子控件的hitTest:withEvent:方法也是會被調(diào)用的须床。
- 事件的響應(yīng)
觸摸事件處理的整體過程
1>用戶點擊屏幕后產(chǎn)生的一個觸摸事件,經(jīng)過一系列的傳遞過程后渐裂,會找到最合適的視圖控件來處理這個事件2>找到最合適的視圖控件后豺旬,就會調(diào)用控件的touches方法來作具體的事件處理touchesBegan…touchesMoved…touchedEnded…3>這些touches方法的默認(rèn)做法是將事件順著響應(yīng)者鏈條向上傳遞(也就是touch方法默認(rèn)不處理事件,只傳遞事件)柒凉,將事件交給上一個響應(yīng)者進(jìn)行處理
響應(yīng)者鏈條:在iOS程序中無論是最后面的UIWindow還是最前面的某個按鈕族阅,它們的擺放是有前后關(guān)系的,一個控件可以放到另一個控件上面或下面膝捞,那么用戶點擊某個控件時是觸發(fā)上面的控件還是下面的控件呢坦刀,這種先后關(guān)系構(gòu)成一個鏈條就叫“響應(yīng)者鏈”。也可以說蔬咬,響應(yīng)者鏈?zhǔn)怯啥鄠€響應(yīng)者對象連接起來的鏈條鲤遥。
事件的傳遞與響應(yīng):
1、當(dāng)一個事件發(fā)生后林艘,事件會從父控件傳給子控件盖奈,也就是說由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的傳遞,也就是尋找最合適的view的過程北启。
2卜朗、接下來是事件的響應(yīng)拔第。首先看initial view能否處理這個事件,如果不能則會將事件傳遞給其上級視圖(inital view的superView)场钉;如果上級視圖仍然無法處理則會繼續(xù)往上傳遞蚊俺;一直傳遞到視圖控制器view controller,首先判斷視圖控制器的根視圖view是否能處理此事件逛万;如果不能則接著判斷該視圖控制器能否處理此事件泳猬,如果還是不能則繼續(xù)向上傳 遞;(對于第二個圖視圖控制器本身還在另一個視圖控制器中宇植,則繼續(xù)交給父視圖控制器的根視圖得封,如果根視圖不能處理則交給父視圖控制器處理);一直到 window指郁,如果window還是不能處理此事件則繼續(xù)交給application處理忙上,如果最后application還是不能處理此事件則將其丟棄。
3闲坎、在事件的響應(yīng)中疫粥,如果某個控件實現(xiàn)了touches...方法,則這個事件將由該控件來接受腰懂,如果調(diào)用了[supertouches….];就會將事件順著響應(yīng)者鏈條往上傳遞梗逮,傳遞給上一個響應(yīng)者;接著就會調(diào)用上一個響應(yīng)者的touches….方法绣溜。
- 如何做到一個事件多個對象處理:
因為系統(tǒng)默認(rèn)做法是把事件上拋給父控件慷彤,所以可以通過重寫自己的touches方法和父控件的touches方法來達(dá)到一個事件多個對象處理的目的。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 1.自己先處理事件...
NSLog(@"do somthing...");
// 2.再調(diào)用系統(tǒng)的默認(rèn)做法怖喻,再把事件交給上一個響應(yīng)者處理
[super touchesBegan:touches withEvent:event];
}
- tips:事件的傳遞和響應(yīng)的區(qū)別:
事件的傳遞是從上到下(父控件到子控件)底哗,事件的響應(yīng)是從下到上(順著響應(yīng)者鏈條向上傳遞:子控件到父控件。
4.dispatch_barrier_async和dispatch_group
- dispatch_barrier_async使用Barrier Task方法Dispatch Barrier解決多線程并發(fā)讀寫同一個資源發(fā)生死鎖
Dispatch Barrier確保提交的閉包是指定隊列中在特定時段唯一在執(zhí)行的一個罢防。在所有先于Dispatch Barrier的任務(wù)都完成的情況下這個閉包才開始執(zhí)行艘虎。輪到這個閉包時barrier會執(zhí)行這個閉包并且確保隊列在此過程不會執(zhí)行其它任務(wù)。閉包完成后隊列恢復(fù)咒吐。需要注意dispatch_barrier_async只在自己創(chuàng)建的隊列上有這種作用,在全局并發(fā)隊列和串行隊列上属划,效果和dispatch_sync一樣恬叹。
- Block組合Dispatch_groups
dispatch groups是專門用來監(jiān)視多個異步任務(wù)。dispatch_group_t實例用來追蹤不同隊列中的不同任務(wù)同眯。
當(dāng)group里所有事件都完成GCD API有兩種方式發(fā)送通知绽昼,第一種是dispatch_group_wait,會阻塞當(dāng)前進(jìn)程须蜗,等所有任務(wù)都完成或等待超時硅确。第二種方法是使用dispatch_group_notify目溉,異步執(zhí)行閉包,不會阻塞菱农。