1.如何理解RunLoop
Runloop(運(yùn)行循環(huán))是iOS和macOS中的一個(gè)核心概念斩芭,它負(fù)責(zé)管理事件和計(jì)時(shí)器乏冀,以確保應(yīng)用程序能夠在正確的時(shí)間響應(yīng)用戶的輸入,并在不占用過多資源的情況下保持活動(dòng)狀態(tài)洋只。
在一個(gè)iOS或macOS應(yīng)用程序中辆沦,有一個(gè)主線程,所有的用戶界面更新识虚、網(wǎng)絡(luò)請(qǐng)求肢扯、定時(shí)器、事件處理等都是在該線程中執(zhí)行的担锤。而 Runloop 就是這個(gè)線程中的一個(gè)對(duì)象蔚晨,它不斷地監(jiān)聽系統(tǒng)事件,如觸摸事件肛循、定時(shí)器事件等铭腕,一旦有事件發(fā)生,就會(huì)通知相應(yīng)的處理方法來處理該事件多糠。
Runloop 監(jiān)聽的事件主要分為兩種:輸入源(Input Source)和定時(shí)源(Timer Source)累舷。輸入源包括觸摸事件、鍵盤事件夹孔、鼠標(biāo)事件等被盈,定時(shí)源則是基于時(shí)間的觸發(fā)器,如 NSTimer搭伤、CADisplayLink 等只怎。當(dāng)事件發(fā)生時(shí),Runloop 會(huì)將事件分發(fā)給相應(yīng)的處理方法怜俐,并在事件處理完成后繼續(xù)監(jiān)聽事件身堡。
Runloop 的好處在于它可以讓應(yīng)用程序在等待事件時(shí)保持活動(dòng)狀態(tài),同時(shí)也能夠在沒有事件時(shí)降低資源占用佑菩。因此盾沫,Runloop 是 iOS 和 macOS 應(yīng)用程序能夠保持流暢和響應(yīng)的關(guān)鍵之一。
2.如何理解RunTime
Runtime(運(yùn)行時(shí))是指程序在運(yùn)行期間的行為殿漠,即代碼被編譯成二進(jìn)制文件之后赴精,在運(yùn)行時(shí)期間的行為。在編譯時(shí)绞幌,我們可以預(yù)測(cè)代碼的行為蕾哟,但是在運(yùn)行時(shí),程序的行為往往會(huì)有一些意外情況,比如動(dòng)態(tài)創(chuàng)建對(duì)象谭确、動(dòng)態(tài)綁定方法等帘营。
在Objective-C中,Runtime是一個(gè)重要的概念逐哈,它為Objective-C提供了一些高級(jí)特性芬迄,如消息傳遞、動(dòng)態(tài)綁定和反射等昂秃。Objective-C的對(duì)象都是在運(yùn)行時(shí)創(chuàng)建的禀梳,它們的屬性和方法也是在運(yùn)行時(shí)添加的。因此肠骆,Objective-C程序的運(yùn)行時(shí)環(huán)境是非常動(dòng)態(tài)的算途。
使用Runtime可以做很多有用的事情,例如:
動(dòng)態(tài)創(chuàng)建類蚀腿、對(duì)象和方法
攔截方法調(diào)用并修改方法實(shí)現(xiàn)
動(dòng)態(tài)交換方法的實(shí)現(xiàn)
實(shí)現(xiàn) KVO(鍵值觀察)功能
實(shí)現(xiàn)消息轉(zhuǎn)發(fā)
總的來說嘴瓤,Runtime是一種強(qiáng)大的工具,它可以幫助我們更好地理解Objective-C的內(nèi)部機(jī)制莉钙,并為我們提供了一些有用的功能廓脆,讓我們能夠更加靈活地編寫高效的代碼。
3.KVO與KVC有什么聯(lián)系
KVC(鍵值編碼)和KVO(鍵值觀察)是Objective-C中的兩個(gè)重要機(jī)制胆胰,它們之間有一定的聯(lián)系狞贱,同時(shí)也有一定的區(qū)別。
KVC是一種通過字符串訪問對(duì)象屬性的機(jī)制蜀涨,它允許我們使用字符串來獲取和設(shè)置對(duì)象的屬性瞎嬉,而不需要顯式地調(diào)用對(duì)象的getter和setter方法。KVC的核心是通過key來訪問對(duì)象的屬性厚柳,即通過字符串訪問對(duì)象的屬性氧枣。
KVO則是一種觀察者模式的實(shí)現(xiàn),它允許一個(gè)對(duì)象監(jiān)聽另一個(gè)對(duì)象的某個(gè)屬性的變化别垮,并在該屬性發(fā)生變化時(shí)自動(dòng)得到通知便监。在iOS開發(fā)中,我們通常使用KVO來實(shí)現(xiàn)對(duì)于UI控件的響應(yīng)碳想、數(shù)據(jù)模型的觀察等場(chǎng)景烧董。
KVC和KVO之間的聯(lián)系在于,KVO的實(shí)現(xiàn)需要使用到KVC胧奔。當(dāng)我們對(duì)一個(gè)對(duì)象的屬性進(jìn)行觀察時(shí)逊移,KVO會(huì)自動(dòng)為被觀察的對(duì)象動(dòng)態(tài)生成一個(gè)子類,并在該子類中重寫被觀察屬性的setter方法龙填。在該setter方法中胳泉,KVO會(huì)添加一些通知的邏輯拐叉,以便在屬性值發(fā)生變化時(shí)發(fā)送通知。
因此扇商,當(dāng)我們使用KVO時(shí)凤瘦,可以通過KVC來訪問被觀察對(duì)象的屬性值。同時(shí)案铺,KVC也可以在不使用KVO的情況下蔬芥,直接訪問對(duì)象的屬性,從而使我們的代碼更加簡(jiǎn)潔和易讀控汉。
需要注意的是坝茎,KVC和KVO雖然在一些場(chǎng)景下會(huì)同時(shí)使用,但是它們是兩個(gè)不同的機(jī)制暇番,應(yīng)該根據(jù)具體的需求來選擇是否使用它們。
4.iOS的事件傳遞過程
事件從UIApplication開始思喊,按照視圖層次結(jié)構(gòu)自頂向下地傳遞壁酬。在這個(gè)過程中恨课,系統(tǒng)會(huì)根據(jù)事件類型和響應(yīng)者鏈來決定將事件發(fā)送給哪個(gè)視圖或控件舆乔。當(dāng)事件傳遞到某個(gè)視圖或控件時(shí),該視圖或控件會(huì)嘗試處理該事件。如果該視圖或控件不能處理該事件这难,則會(huì)將該事件轉(zhuǎn)發(fā)給下一個(gè)響應(yīng)者禁偎。這個(gè)過程會(huì)一直持續(xù)到某個(gè)響應(yīng)者處理了該事件枷遂,或者事件傳遞到UIApplication時(shí)結(jié)束痪伦。
當(dāng)事件處理完成后侄榴,系統(tǒng)會(huì)對(duì)事件做出最終的響應(yīng)。例如流妻,在觸摸事件中牲蜀,系統(tǒng)會(huì)根據(jù)手指的狀態(tài)來判斷該事件是否是一個(gè)單擊、雙擊绅这、長(zhǎng)按等手勢(shì)涣达。同時(shí),系統(tǒng)還會(huì)在事件結(jié)束時(shí)對(duì)相關(guān)的UI元素進(jìn)行更新和重繪证薇。
在事件傳遞過程中度苔,可以通過重寫視圖或控件的響應(yīng)方法來改變事件的傳遞和處理行為。例如浑度,可以重寫hitTest:withEvent:方法來指定事件的響應(yīng)對(duì)象寇窑;重寫pointInside:withEvent:方法來指定響應(yīng)區(qū)域;重寫touchesBegan:withEvent:方法來處理觸摸事件等箩张。
總之甩骏,在iOS中窗市,事件傳遞過程是一個(gè)復(fù)雜而又精細(xì)的機(jī)制。了解事件傳遞過程對(duì)于優(yōu)化應(yīng)用程序的響應(yīng)速度和用戶體驗(yàn)有著重要的意義饮笛。
5.CALayer與UIView的關(guān)系
CALayer是UIView的底層實(shí)現(xiàn)咨察,可以看作是UIView的圖層。每個(gè)UIView都有一個(gè)對(duì)應(yīng)的CALayer對(duì)象福青,它負(fù)責(zé)管理UIView的內(nèi)容和顯示摄狱。UIView提供了一些高級(jí)的界面控制功能,例如響應(yīng)用戶交互事件无午、布局管理媒役、自動(dòng)繪制等,而CALayer則負(fù)責(zé)渲染UIView的內(nèi)容宪迟,包括繪制酣衷、變換、動(dòng)畫等次泽。
UIView和CALayer的關(guān)系可以用以下兩種方式來理解:
- UIView是CALayer的外觀:UIView提供了許多高級(jí)的界面控制功能鸥诽,例如用戶交互、布局管理等箕憾,這些功能都建立在CALayer的基礎(chǔ)之上。因此拳昌,可以將UIView看作是CALayer的外觀袭异,它通過對(duì)CALayer的屬性進(jìn)行設(shè)置,來控制圖層的顯示和行為炬藤。
- UIView是CALayer的管理者:UIView負(fù)責(zé)管理CALayer對(duì)象的創(chuàng)建御铃、銷毀、布局等沈矿,它維護(hù)了一個(gè)CALayer的層級(jí)結(jié)構(gòu)上真,并負(fù)責(zé)對(duì)圖層的屬性進(jìn)行設(shè)置。UIView還提供了一些高級(jí)的功能羹膳,例如自動(dòng)繪制睡互、動(dòng)畫效果等,這些功能都是基于CALayer實(shí)現(xiàn)的陵像。
因此就珠,可以說CALayer是UIView的底層實(shí)現(xiàn),UIView提供了對(duì)CALayer的封裝醒颖,使得開發(fā)者可以更加方便地使用和管理圖層妻怎。在開發(fā)中,如果需要實(shí)現(xiàn)一些高級(jí)的界面效果泞歉,例如復(fù)雜的動(dòng)畫逼侦、圖層蒙版等匿辩,就需要直接使用CALayer來實(shí)現(xiàn)。
6.iOS中為什么代理需要用weak修飾
- 避免循環(huán)引用:如果代理對(duì)象使用strong來修飾榛丢,那么代理對(duì)象就會(huì)對(duì)委托對(duì)象進(jìn)行強(qiáng)引用铲球,而委托對(duì)象又會(huì)對(duì)代理對(duì)象進(jìn)行引用,從而形成循環(huán)引用涕滋。這樣就會(huì)導(dǎo)致對(duì)象無法釋放睬辐,造成內(nèi)存泄漏。因此宾肺,使用weak來修飾代理對(duì)象可以避免循環(huán)引用問題溯饵。
- 被代理對(duì)象通常具有更長(zhǎng)的生命周期:在大多數(shù)情況下,代理對(duì)象是由被代理對(duì)象持有的锨用,因此被代理對(duì)象通常具有更長(zhǎng)的生命周期丰刊。如果使用strong來修飾代理對(duì)象,那么代理對(duì)象就會(huì)一直存在于內(nèi)存中增拥,即使被代理對(duì)象已經(jīng)不存在了啄巧。這樣就會(huì)造成內(nèi)存浪費(fèi)。
總之掌栅,使用weak來修飾代理對(duì)象可以避免循環(huán)引用問題秩仆,同時(shí)也更符合代理對(duì)象與被代理對(duì)象之間的實(shí)際關(guān)系。
7.Block為什么要用copy修飾
Block在定義時(shí)會(huì)捕獲作用域中的變量和對(duì)象猾封,并將其保存在一個(gè)數(shù)據(jù)結(jié)構(gòu)中澄耍,以便在后續(xù)的調(diào)用中使用。
由于Block可能在定義時(shí)保存了局部變量或?qū)ο蟮囊蒙卧担绻贐lock在定義后延遲執(zhí)行時(shí)齐莲,這些局部變量或?qū)ο笠呀?jīng)被銷毀,那么在Block執(zhí)行時(shí)就會(huì)訪問到已經(jīng)釋放的內(nèi)存空間磷箕,導(dǎo)致程序崩潰选酗。因此,為了避免這種問題岳枷,Block需要使用copy修飾芒填。
使用copy修飾Block會(huì)將其從棧區(qū)復(fù)制到堆區(qū),這樣就能保證在Block執(zhí)行時(shí)空繁,其所依賴的對(duì)象和變量的引用不會(huì)因?yàn)樽饔糜虻母淖兌夂妗>唧w來說,當(dāng)Block被復(fù)制到堆區(qū)時(shí)家厌,其所依賴的對(duì)象和變量也會(huì)被一并復(fù)制到堆區(qū)播玖,這樣就能保證在Block執(zhí)行時(shí),其所依賴的對(duì)象和變量的引用是有效的饭于。
需要注意的是蜀踏,在ARC(Automatic Reference Counting)環(huán)境下维蒙,如果Block沒有捕獲任何對(duì)象,則可以使用strong來修飾果覆,因?yàn)檫@種情況下Block不會(huì)持有任何對(duì)象的引用颅痊。但是如果Block捕獲了對(duì)象或變量,則仍然需要使用copy來修飾局待,以確保Block的正確性斑响。
block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū)钳榨;在 ARC 中寫不寫都行舰罚。
對(duì)于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅薛耻,還能時(shí)刻提醒我們:編譯器自動(dòng)對(duì) block 進(jìn)行了 copy 操作营罢。如果不寫 copy ,該類的調(diào)用者有可能會(huì)忘記或者根本不知道“編譯器會(huì)自動(dòng)對(duì) block 進(jìn)行了 copy 操作”饼齿,他們有可能會(huì)在調(diào)用之前自行拷貝屬性值饲漾。這種操作多余而低效。
8.什么是Block
Block是一種用于封裝一段代碼的數(shù)據(jù)類型缕溉。Block實(shí)際上是一個(gè)匿名函數(shù)考传,它可以捕獲一些變量和常量,并將它們封裝在一起证鸥,形成一個(gè)可以在需要時(shí)執(zhí)行的代碼塊伙菊。Block可以被當(dāng)作一個(gè)對(duì)象來使用,它可以作為方法參數(shù)敌土、成員變量、局部變量运翼、數(shù)組元素等等返干。Block中捕獲的變量和常量被保存在Block中,可以在Block執(zhí)行時(shí)使用血淌。
在使用Block時(shí)矩欠,需要注意以下幾點(diǎn):
- Block中捕獲的變量需要在Block執(zhí)行時(shí)仍然存在。如果Block中捕獲的變量是局部變量悠夯,那么需要使用__block修飾符來使其在Block執(zhí)行時(shí)仍然存在癌淮。
- Block中使用的變量需要正確地被引用。如果Block中使用了對(duì)象沦补,那么需要使用strong或weak修飾符來指定對(duì)該對(duì)象的引用方式乳蓄。如果Block中使用了基本數(shù)據(jù)類型的變量,則不需要使用修飾符夕膀。
- Block可以作為參數(shù)傳遞給方法或函數(shù)虚倒,也可以在方法或函數(shù)中定義美侦。如果Block作為參數(shù)傳遞給方法或函數(shù),則需要使用^符號(hào)定義魂奥。
總之菠剩,Block是一種非常強(qiáng)大和靈活的數(shù)據(jù)類型,它可以用于封裝一段代碼耻煤,并將其當(dāng)做一個(gè)對(duì)象來使用具壮。Block可以捕獲變量和常量,并且可以作為參數(shù)傳遞給方法或函數(shù)哈蝇,在實(shí)際開發(fā)中非常常用棺妓。
9.iOS是如何實(shí)現(xiàn)APNs的
APNs(Apple Push Notification service)是蘋果提供的推送服務(wù),它允許開發(fā)者向用戶設(shè)備發(fā)送推送通知买鸽。iOS系統(tǒng)實(shí)現(xiàn)APNs的過程如下:
- iOS應(yīng)用程序需要使用APNs推送服務(wù)涧郊,需要首先向APNs服務(wù)器注冊(cè)。
- 注冊(cè)過程中眼五,iOS應(yīng)用程序會(huì)生成一個(gè)設(shè)備令牌(Device Token)妆艘,并將該令牌發(fā)送給APNs服務(wù)器。設(shè)備令牌是一個(gè)唯一標(biāo)識(shí)符看幼,用于標(biāo)識(shí)該設(shè)備的APNs推送服務(wù)批旺。
- APNs服務(wù)器會(huì)將設(shè)備令牌保存在其數(shù)據(jù)庫(kù)中,并為該設(shè)備生成一個(gè)注冊(cè)ID(Registration ID)诵姜,用于標(biāo)識(shí)該設(shè)備在APNs服務(wù)器上的注冊(cè)信息汽煮。
當(dāng)開發(fā)者需要向某個(gè)設(shè)備發(fā)送推送通知時(shí),需要將推送通知發(fā)送給APNs服務(wù)器棚唆。 - APNs服務(wù)器會(huì)根據(jù)設(shè)備令牌和注冊(cè)ID暇赤,將推送通知發(fā)送到對(duì)應(yīng)的設(shè)備上。
接收到推送通知的設(shè)備會(huì)顯示通知內(nèi)容宵凌,并執(zhí)行相應(yīng)的操作鞋囊。
在實(shí)際開發(fā)中,iOS應(yīng)用程序可以通過使用APNs SDK瞎惫,來向APNs服務(wù)器注冊(cè)溜腐、發(fā)送推送通知等操作。具體來說瓜喇,可以使用APNs SDK中提供的API挺益,通過創(chuàng)建連接、發(fā)送請(qǐng)求等方式乘寒,來實(shí)現(xiàn)對(duì)APNs服務(wù)器的訪問和操作望众。同時(shí),iOS應(yīng)用程序也需要提供一些必要的配置信息,如證書黍檩、證書密碼叉袍、設(shè)備令牌等等,以便與APNs服務(wù)器進(jìn)行通信
10.談?wù)剬?duì)內(nèi)存管理的理解
在iOS中刽酱,主要采用了自動(dòng)引用計(jì)數(shù)(Automatic Reference Counting喳逛,ARC)的機(jī)制來進(jìn)行內(nèi)存管理。ARC是一種編譯時(shí)自動(dòng)插入內(nèi)存管理代碼的技術(shù)棵里,它可以自動(dòng)地分析對(duì)象的引用關(guān)系润文,并在對(duì)象不再被使用時(shí)自動(dòng)釋放對(duì)象所占用的內(nèi)存。ARC機(jī)制可以極大地簡(jiǎn)化內(nèi)存管理的過程殿怜,減少了手動(dòng)釋放內(nèi)存的工作量典蝌,提高了應(yīng)用程序的性能和穩(wěn)定性。
在使用ARC時(shí)头谜,需要注意以下幾點(diǎn):
- 對(duì)象的引用關(guān)系需要正確地維護(hù)骏掀。如果對(duì)象被多個(gè)變量引用,那么需要確保所有引用都正確地被處理柱告。
- 對(duì)象的循環(huán)引用需要避免截驮。循環(huán)引用會(huì)導(dǎo)致對(duì)象無法被正確地釋放,從而導(dǎo)致內(nèi)存泄漏际度。
- 對(duì)象的生命周期需要正確地管理葵袭。對(duì)象在不再被使用時(shí)應(yīng)該及時(shí)釋放,以便及時(shí)回收內(nèi)存資源乖菱。
除了ARC機(jī)制外坡锡,iOS還提供了一些手動(dòng)管理內(nèi)存的方式,如MRC(Manual Reference Counting)和內(nèi)存池技術(shù)等窒所。在使用這些技術(shù)時(shí)鹉勒,需要更加謹(jǐn)慎地管理對(duì)象的引用關(guān)系和生命周期,以免出現(xiàn)內(nèi)存泄漏等問題吵取。
11.什么是內(nèi)存池
內(nèi)存池是一種優(yōu)化內(nèi)存分配和管理的技術(shù)禽额,它可以提高內(nèi)存分配和釋放的效率,減少因頻繁申請(qǐng)和釋放內(nèi)存而帶來的性能損失海渊。
內(nèi)存池的基本思想是在程序初始化時(shí)預(yù)先分配一定數(shù)量的內(nèi)存塊,并把它們存放在一個(gè)池子里面哲鸳。當(dāng)需要申請(qǐng)內(nèi)存時(shí)臣疑,不再使用系統(tǒng)提供的malloc()或new運(yùn)算符來動(dòng)態(tài)申請(qǐng)內(nèi)存盯仪,而是直接從內(nèi)存池中取出一個(gè)內(nèi)存塊進(jìn)行使用疏咐。當(dāng)不再需要這個(gè)內(nèi)存塊時(shí)实昨,也不用立即將其釋放滔驶,而是將其放回內(nèi)存池中粟瞬,以備后續(xù)使用。
內(nèi)存池的優(yōu)點(diǎn)主要有:
- 減少內(nèi)存分配和釋放的次數(shù)宇攻,降低內(nèi)存碎片的產(chǎn)生型雳,提高內(nèi)存分配和釋放的效率。
- 程序初始化時(shí)可以預(yù)先分配一定數(shù)量的內(nèi)存挤茄,避免在程序運(yùn)行過程中頻繁地進(jìn)行內(nèi)存分配和釋放如叼,提高程序的性能。
- 內(nèi)存池可以減少內(nèi)存申請(qǐng)和釋放帶來的鎖競(jìng)爭(zhēng)穷劈,從而提高程序的并發(fā)性能笼恰。
在iOS開發(fā)中,內(nèi)存池的應(yīng)用比較廣泛歇终,例如在網(wǎng)絡(luò)編程中使用的Socket連接池社证、數(shù)據(jù)庫(kù)連接池、圖片緩存池等评凝。內(nèi)存池技術(shù)可以有效地優(yōu)化內(nèi)存管理追葡,提高程序的性能和穩(wěn)定性,但需要注意合理地設(shè)計(jì)內(nèi)存池的大小和對(duì)象的生命周期奕短,以避免因內(nèi)存池過大或過小而造成的資源浪費(fèi)或性能損失宜肉。
12.為什么要使用__weak
13.為什么Block使用了__block,就可以修改block內(nèi)部的變量
__block修飾符標(biāo)記后篡诽,block就會(huì)訪問標(biāo)記變量本身內(nèi)存地址崖飘,而未標(biāo)記對(duì)象則訪問截獲拷貝后的變量的內(nèi)存地址
14.iOS的啟動(dòng)優(yōu)化都有哪些
15.闡述一下APP的啟動(dòng)流程
1、系統(tǒng)先讀取App的可執(zhí)行文件(Mach-O文件)杈女,獲取到dyld(動(dòng)態(tài)庫(kù))的路徑朱浴,并加載dyld(動(dòng)態(tài)庫(kù)鏈接程序)。
2达椰、dyld去初始化運(yùn)行環(huán)境翰蠢、開啟緩存策略(冷熱啟動(dòng))、加載依賴庫(kù)(讀取文件啰劲、驗(yàn)證梁沧、注冊(cè)到系統(tǒng)核心)、我們的可執(zhí)行文件蝇裤、鏈接依賴庫(kù)廷支,并調(diào)用每個(gè)依賴庫(kù)的初始化方法。
3栓辜、在上一步runtime被初始化恋拍,當(dāng)所有的依賴庫(kù)初始化后,程序可執(zhí)行文件進(jìn)行初始化藕甩,這個(gè)時(shí)候runtime會(huì)對(duì)項(xiàng)目中的所有類進(jìn)行類結(jié)構(gòu)初始化施敢,然后調(diào)用所有類的+load方法。
1、runtime初始化方法 _objc_init 中最后注冊(cè)了兩個(gè)通知:
map_images: 主要是在鏡像加載進(jìn)內(nèi)容后對(duì)其二進(jìn)制內(nèi)容進(jìn)行解析僵娃,初始化里面的類結(jié)構(gòu)等
load_images: 主要是調(diào)用call_load_methods 按照繼承層次依次調(diào)用Class的 +load方法 然后是Category的+ load方法概作。(call_load_methods 調(diào)用load 是通過方法地址直接調(diào)用的load方法,并不是通過消息機(jī)制默怨,這就是為什么分類中的load方法并不會(huì)覆蓋主類以及其他同主類的分類里的load 方法實(shí)現(xiàn)了讯榕。)
2、runtime 調(diào)用項(xiàng)目中所有的load方法時(shí)先壕,所有的類的結(jié)構(gòu)已經(jīng)初始化了,此時(shí)在load方法中可以使用任何類創(chuàng)建實(shí)例并給他們發(fā)送消息瘩扼。
4、最后dyld返回main函數(shù)地址垃僚,main函數(shù)被調(diào)用集绰。dyld會(huì)緩存上一次把信息加載內(nèi)存的緩存,所以第二次比第一次啟動(dòng)快一點(diǎn)谆棺。
pre-main
初始化空間栽燕,加載鏡像,載入動(dòng)態(tài)庫(kù)鏈接器改淑,鏈接動(dòng)態(tài)庫(kù)碍岔,objcsetup,callloadmethod朵夏,
動(dòng)態(tài)庫(kù)越多蔼啦,啟動(dòng)越慢,+load方法多也會(huì)影響
優(yōu)化方向:靜態(tài)庫(kù)仰猖,cocoapods :linkage => :static指令 將所有的動(dòng)態(tài)庫(kù)轉(zhuǎn)為靜態(tài)庫(kù)捏肢。 要注意資源獲取的方式問題。
合并動(dòng)態(tài)庫(kù):cocoapods-pod-merge 支持合并動(dòng)態(tài)庫(kù)饥侵,要注意import方式會(huì)改變鸵赫,作用域前綴要加上
main
main函數(shù)以后就是盡量減少第一個(gè)頁面展示前的工作,有些組件庫(kù)自己的工作可以派發(fā)到庫(kù)自己處理躏升,或者異步處理
啟動(dòng)項(xiàng)熱拔插
main函數(shù)之前優(yōu)化
啟動(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ù)量佃却。
Rebase和Bind都是為了解決指針引用的問題者吁。對(duì)于Objective C開發(fā)來說,主要的時(shí)間消耗在Class/Method的符號(hào)加載上双霍,所以常見的優(yōu)化方案是:
合并Category和功能類似的類砚偶。比如:UIView+Frame,UIView+AutoLayout…合并為一個(gè)
刪除無用的方法和類。
16.KVC的原理
1.KVC是基于runtime機(jī)制實(shí)現(xiàn)的
2洒闸、可以訪問私有成員變量染坯、可以間接修改私有變量的值
KVC鍵值查找原理
setValue:forKey:搜索方式
1、首先搜索setKey:方法.(key指成員變量名, 首字母大寫)
2丘逸、上面的setter方法沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey单鹿,key, iskey的順序搜索成員名。(這個(gè)類方法是NSKeyValueCodingCatogery中實(shí)現(xiàn)的類方法, 默認(rèn)實(shí)現(xiàn)為返回YES)
3深纲、如果沒有找到成員變量, 調(diào)用setValue:forUnderfinedKey:
valueForKey:的搜索方式
1仲锄、首先按getKey, key, isKey的順序查找getter方法, 找到直接調(diào)用. 如果是BOOL、int等內(nèi)建值類型, 會(huì)做NSNumber的轉(zhuǎn)換.
2湃鹊、上面的getter沒找到, 查找countOfKey, objectInKeyAtindex, KeyAtindexes格式的方法. 如果countOfKey和另外兩個(gè)方法中的一個(gè)找到, 那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所有方法的代理集合的NSArray消息方法.
3儒喊、還沒找到, 查找countOfKey, enumeratorOfKey, memberOfKey格式的方法. 如果這三個(gè)方法都找到, 那么就返回一個(gè)可以響應(yīng)NSSet所有方法的代理集合.
4、還是沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey, key, iskey的順序搜索成員名.
5币呵、再?zèng)]找到, 調(diào)用valueForUndefinedKey.
17.KVO原理
1.KVO是基于runtime機(jī)制實(shí)現(xiàn)的
2.當(dāng)某個(gè)類的屬性對(duì)象第一次被觀察時(shí)怀愧,系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的setter 方法余赢。派生類在被重寫的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制
3.如果原類為Person芯义,那么生成的派生類名為NSKVONotifying_Person
4.每個(gè)類對(duì)象中都有一個(gè)isa指針指向當(dāng)前類,當(dāng)一個(gè)類對(duì)象的第一次被觀察妻柒,那么系統(tǒng)會(huì)偷偷將isa指針指向動(dòng)態(tài)生成的派生類扛拨,從而在給被監(jiān)控屬性賦值時(shí)執(zhí)行的是派生類的setter方法
5.鍵值觀察通知依賴于NSObject 的兩個(gè)方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個(gè)被觀察屬性發(fā)生改變之前举塔, willChangeValueForKey:一定會(huì)被調(diào)用绑警,這就 會(huì)記錄舊的值。而當(dāng)改變發(fā)生后啤贩,didChangeValueForKey:會(huì)被調(diào)用待秃,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用。
深入
1.Apple 使用了 isa 混寫(isa-swizzling)來實(shí)現(xiàn) KVO 痹屹。當(dāng)觀察對(duì)象A時(shí)章郁,KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為:?NSKVONotifying_A的新類,該類繼承自對(duì)象A的本類志衍,且KVO為NSKVONotifying_A重寫觀察屬性的setter?方法暖庄,setter?方法會(huì)負(fù)責(zé)在調(diào)用原?setter?方法之前和之后,通知所有觀察對(duì)象屬性值的更改情況楼肪。
2.NSKVONotifying_A類剖析:在這個(gè)過程培廓,被觀察對(duì)象的 isa 指針從指向原來的A類,被KVO機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_A類春叫,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽肩钠;
3.所以當(dāng)我們從應(yīng)用層面上看來泣港,完全沒有意識(shí)到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì)KVO的底層實(shí)現(xiàn)過程价匠,讓我們誤以為還是原來的類当纱。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為“NSKVONotifying_A”的類(),就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè)KVO的那段代碼時(shí)程序就崩潰踩窖,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽的時(shí)候動(dòng)態(tài)創(chuàng)建了名為NSKVONotifying_A的中間類坡氯,并指向這個(gè)中間類了。
4.(isa 指針的作用:每個(gè)對(duì)象都有isa 指針洋腮,指向該對(duì)象的類箫柳,它告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么。所以對(duì)象注冊(cè)為觀察者時(shí)啥供,isa指針指向新子類悯恍,那么這個(gè)被觀察的對(duì)象就神奇地變成新子類的對(duì)象(或?qū)嵗┝恕#?因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用已重寫的 setter伙狐,從而激活鍵值通知機(jī)制坪稽。
5.子類setter方法剖析:KVO的鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數(shù)值的前后分別調(diào)用2個(gè)方法: 被觀察屬性發(fā)生改變之前鳞骤,willChangeValueForKey:被調(diào)用窒百,通知系統(tǒng)該 keyPath?的屬性值即將變更;當(dāng)改變發(fā)生后豫尽, didChangeValueForKey: 被調(diào)用篙梢,通知系統(tǒng)該 keyPath?的屬性值已經(jīng)變更;之后美旧,?observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用渤滞。且重寫觀察屬性的setter?方法這種繼承方式的注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。
18.category為什么不能添加屬性榴嗅?
category 它是在運(yùn)行期決議的妄呕,因?yàn)樵谶\(yùn)行期,對(duì)象的內(nèi)存布局已經(jīng)確定嗽测,如果添加實(shí)例變量就會(huì)破壞類的內(nèi)部布局绪励,這對(duì)編譯型語言來說是災(zāi)難性的。
extension看起來很像一個(gè)匿名的category唠粥,但是extension和有名字的category幾乎完全是兩個(gè)東西疏魏。 extension在編譯期決議,它就是類的一部分晤愧,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類大莫,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡官份。extension一般用來隱藏類的私有信息只厘,你必須有一個(gè)類的源碼才能為一個(gè)類添加extension烙丛,所以你無法為系統(tǒng)的類比如NSString添加extension。
但是category則完全不一樣羔味,它是在運(yùn)行期決議的蜀变。
就category和extension的區(qū)別來看,我們可以推導(dǎo)出一個(gè)明顯的事實(shí)介评,extension可以添加實(shí)例變量,而category是無法添加實(shí)例變量的爬舰。
那為什么 使用Runtime技術(shù)中的關(guān)聯(lián)對(duì)象可以為類別添加屬性们陆。
其原因是:關(guān)聯(lián)對(duì)象都由AssociationsManager管理,AssociationsManager里面是由一個(gè)靜態(tài)AssociationsHashMap來存儲(chǔ)所有的關(guān)聯(lián)對(duì)象的情屹。這相當(dāng)于把所有對(duì)象的關(guān)聯(lián)對(duì)象都存在一個(gè)全局map里面坪仇。而map的的key是這個(gè)對(duì)象的指針地址(任意兩個(gè)不同對(duì)象的指針地址一定是不同的),而這個(gè)map的value又是另外一個(gè)AssociationsHashMap垃你,里面保存了關(guān)聯(lián)對(duì)象的kv對(duì)椅文。
如合清理關(guān)聯(lián)對(duì)象?
runtime的銷毀對(duì)象函數(shù)objc_destructInstance里面會(huì)判斷這個(gè)對(duì)象有沒有關(guān)聯(lián)對(duì)象惜颇,如果有皆刺,會(huì)調(diào)用_object_remove_assocations做關(guān)聯(lián)對(duì)象的清理工作。
19.類擴(kuò)展和分類的區(qū)別
- 分類不能添加成員變量【雖然可以添加屬性 但是一旦調(diào)用就會(huì)報(bào)方法找不到的錯(cuò)誤】 (可以通過runtime給分類間接添加成員變量),而類擴(kuò)展可以添加成員變量凌摄;
- 分類中的屬性不會(huì)自動(dòng)實(shí)現(xiàn)set方法和get方法羡蛾,而類擴(kuò)展中的屬性再轉(zhuǎn)為底層時(shí)是可以自動(dòng)實(shí)現(xiàn)set、get方法
- 類擴(kuò)展中添加的新方法锨亏,不實(shí)現(xiàn)會(huì)報(bào)警告痴怨。categorygory中定義了方法不實(shí)現(xiàn)則沒有這個(gè)問題
- 類擴(kuò)展可以定義在.m文件中,這種擴(kuò)展方式中定義的變量都是私有的器予,也可以定義在.h文件中浪藻,這樣定義的代碼就是共有的,類擴(kuò)展在.m文件中聲明私有方法是非常好的方式乾翔。
- 類擴(kuò)展不能像分類那樣擁有獨(dú)立的實(shí)現(xiàn)部分(@implementation部分)爱葵,也就是說,類擴(kuò)展所聲明的方法必須依托對(duì)應(yīng)類的實(shí)現(xiàn)部分來實(shí)現(xiàn)反浓。
20.App的啟動(dòng)流程
main函數(shù)之前
系統(tǒng)將App的可執(zhí)行文件(Mach-O文件)和dyld加載到內(nèi)存钧惧,由dyld進(jìn)行動(dòng)態(tài)鏈接。
設(shè)置相關(guān)環(huán)境變量
根據(jù)環(huán)境變量設(shè)置相應(yīng)的值以及獲取當(dāng)前運(yùn)行架構(gòu)勾习。例如配置環(huán)境變量打印啟動(dòng)流程耗時(shí): DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS浓瞪。加載共享緩存庫(kù)
加載動(dòng)態(tài)共享緩存庫(kù)到動(dòng)態(tài)庫(kù)共享緩存區(qū),例如UIKit巧婶、CoreFoundation等官方庫(kù)乾颁。加載動(dòng)態(tài)庫(kù)
把所有的可執(zhí)行文件所依賴的動(dòng)態(tài)庫(kù)遞歸加載到內(nèi)存中涂乌。rebase和binding
iOS采用ASLR技術(shù)(地址空間布局隨機(jī)化),加載App的內(nèi)存地址是隨機(jī)的英岭,rebase會(huì)根據(jù)隨機(jī)的偏移量對(duì)原來的地址做重定向湾盒。
binding進(jìn)行符號(hào)綁定。指向image外部動(dòng)態(tài)庫(kù)的指針被符號(hào)(symbol)綁定诅妹。dyld需要去符號(hào)表里查找罚勾,找到對(duì)應(yīng)的實(shí)現(xiàn)。Objc setup
(1)注冊(cè)O(shè)bjC類
(2)把category的定義插入方法列表
(3)selector唯一性檢查initializer
(1)調(diào)用所有類吭狡、分類的+load方法
(2)調(diào)用attribute((constructor))修飾的函數(shù)
(3)非基本類型的C++靜態(tài)全局變量的創(chuàng)建(通常是類或結(jié)構(gòu)體)
map_images與load_images
map_images : dyld 將 image 加載進(jìn)內(nèi)存時(shí) , 會(huì)觸發(fā)該函數(shù).
load_images : dyld 初始化 image 會(huì)觸發(fā)該方法. ( 我們所熟知的 load 方法也是在此處調(diào)用 ) .
dyld在初始化其他動(dòng)態(tài)庫(kù)之前尖殃,會(huì)最先初始化系統(tǒng)庫(kù)libsystem,運(yùn)行Runtime划煮。系統(tǒng)庫(kù)libsystem初始化完成后送丰,就會(huì)初始化其他動(dòng)態(tài)庫(kù),然后由Runtime調(diào)用map_images來讀取類弛秋、方法器躏、協(xié)議以及分類并存儲(chǔ)到對(duì)應(yīng)的表中(注意:分類并不是直接存,而是通過attachLists方法把分類的數(shù)據(jù)添加到類里面)蟹略,然后Runtime會(huì)繼續(xù)調(diào)用load_images調(diào)用所有類的load方法以及分類的load方法登失,這些都做完之后,通過dyld提供的回調(diào)_dyld_objc_notify_register挖炬,告訴dyld加載完畢壁畸,然后dyld就開始找主程序的入口main函數(shù),最后進(jìn)入程序的main函數(shù)茅茂。
- load方法的調(diào)用順序
+load方法是在load_images中調(diào)用的捏萍。
load方法調(diào)用順序?yàn)椋合忍幚眍悾筇幚矸诸惪障校惶幚眍惖捻樞蚴窍雀割惲铊荆笞宇?br> 在調(diào)用類的load方法時(shí),做了遞歸處理碴倾,會(huì)先調(diào)用父類的load逗噩,然后再調(diào)用子類的load,所有類的load方法調(diào)用完成后跌榔,才會(huì)開始處理所有類的分類异雁,分類的處理順序取決于Mach-O頭文件,和類的順序沒有直接關(guān)系僧须。先后順序即:父類->子類->所有類的分類纲刀。
main函數(shù)階段
main() 會(huì)調(diào)用 UIApplicationMain(),直至application:didFinishLaunchingWithOptions:執(zhí)行完畢担平,整個(gè)啟動(dòng)流程就完成了示绊。
21.談?wù)剬?duì)單例模式的理解
單例模式是一種設(shè)計(jì)模式锭部,它確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)來訪問該實(shí)例面褐。單例模式通常用于需要全局唯一對(duì)象的場(chǎng)景拌禾,比如配置管理、資源管理展哭、日志記錄等湃窍。
(1)唯一實(shí)例: 單例模式確保一個(gè)類只有一個(gè)實(shí)例存在,無論何時(shí)何地匪傍,對(duì)該類的實(shí)例的訪問都只會(huì)返回同一個(gè)對(duì)象您市。這樣可以避免創(chuàng)建多個(gè)對(duì)象造成資源的浪費(fèi),同時(shí)保證對(duì)象的唯一性析恢。
(2)全局訪問點(diǎn): 單例模式提供了一個(gè)全局訪問點(diǎn)來訪問唯一的實(shí)例,使得其他對(duì)象可以通過該訪問點(diǎn)獲取單例對(duì)象的引用秧饮。這樣可以方便地在程序的任何地方使用單例對(duì)象映挂,而不需要顯式地創(chuàng)建對(duì)象或傳遞對(duì)象的引用。
(3)延遲實(shí)例化: 單例模式通常采用延遲實(shí)例化的方式來創(chuàng)建單例對(duì)象盗尸,即在第一次訪問單例對(duì)象時(shí)才創(chuàng)建對(duì)象柑船。這樣可以節(jié)省資源,避免在程序啟動(dòng)時(shí)就創(chuàng)建對(duì)象泼各,提高程序的啟動(dòng)速度鞍时。
(4)線程安全性: 在多線程環(huán)境下,單例模式需要考慮線程安全性的問題扣蜻,確保在多線程環(huán)境下也能正確地創(chuàng)建和訪問單例對(duì)象逆巍。常見的線程安全實(shí)現(xiàn)方式包括使用同步鎖、靜態(tài)變量初始化莽使、延遲初始化等锐极。
(5)生命周期管理: 單例模式通常是全局存在的,因此需要考慮對(duì)象的生命周期管理芳肌,確保單例對(duì)象在合適的時(shí)機(jī)被正確地釋放和清理灵再。這樣可以避免內(nèi)存泄漏和資源泄漏問題。
(6)全局狀態(tài)共享: 單例模式可以用來實(shí)現(xiàn)全局狀態(tài)共享亿笤,多個(gè)對(duì)象可以共享同一個(gè)單例對(duì)象的狀態(tài)翎迁,從而實(shí)現(xiàn)數(shù)據(jù)共享和通信。
總的來說净薛,單例模式是一種簡(jiǎn)單而又實(shí)用的設(shè)計(jì)模式汪榔,它提供了一種方便的方式來管理全局唯一對(duì)象,并確保對(duì)象的唯一性肃拜、延遲實(shí)例化揍异、線程安全性等特性全陨,適用于許多需要全局唯一對(duì)象的場(chǎng)景。但是在使用單例模式時(shí)需要注意線程安全性和生命周期管理等問題衷掷,以確保單例對(duì)象的正確使用和管理辱姨。
22.談一談對(duì)runtime的理解
(1)動(dòng)態(tài)性: Runtime 是 Objective-C 的運(yùn)行時(shí)系統(tǒng),它允許在程序運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建類戚嗅、修改類的結(jié)構(gòu)雨涛、調(diào)用方法等。這種動(dòng)態(tài)性使得 Objective-C 具有很強(qiáng)的靈活性懦胞,可以實(shí)現(xiàn)一些在靜態(tài)語言中難以實(shí)現(xiàn)的功能替久,比如動(dòng)態(tài)派發(fā)、消息轉(zhuǎn)發(fā)等躏尉。
(2)元數(shù)據(jù): Runtime 中存儲(chǔ)了關(guān)于類蚯根、對(duì)象、方法等的元數(shù)據(jù)信息胀糜,包括類的名稱颅拦、父類、實(shí)例變量教藻、方法列表等距帅。通過這些元數(shù)據(jù)信息,可以在運(yùn)行時(shí)對(duì)類和對(duì)象進(jìn)行操作括堤,比如動(dòng)態(tài)創(chuàng)建類和對(duì)象碌秸、動(dòng)態(tài)添加方法等。
(3)消息傳遞: 在 Objective-C 中悄窃,方法調(diào)用是通過消息傳遞的方式實(shí)現(xiàn)的讥电,即發(fā)送一個(gè)消息給對(duì)象,對(duì)象根據(jù)消息選擇合適的方法來執(zhí)行轧抗。Runtime 提供了消息傳遞的機(jī)制允趟,包括消息發(fā)送、方法解析鸦致、消息轉(zhuǎn)發(fā)等潮剪,使得 Objective-C 具有動(dòng)態(tài)派發(fā)的特性。
(4)方法調(diào)用: Runtime 提供了一系列方法來實(shí)現(xiàn)方法的調(diào)用分唾,包括實(shí)例方法調(diào)用抗碰、類方法調(diào)用、動(dòng)態(tài)方法解析绽乔、方法交換等弧蝇。通過這些方法,可以在運(yùn)行時(shí)動(dòng)態(tài)地調(diào)用方法,甚至可以修改方法的實(shí)現(xiàn)看疗。
(5)內(nèi)存管理: Runtime 還提供了一些內(nèi)存管理的功能沙峻,包括自動(dòng)引用計(jì)數(shù)(ARC)和自動(dòng)釋放池(Autorelease Pool)。通過這些功能两芳,可以在運(yùn)行時(shí)對(duì)對(duì)象的內(nèi)存進(jìn)行管理摔寨,確保對(duì)象的引用計(jì)數(shù)正確,避免內(nèi)存泄漏和野指針問題怖辆。
綜上所述是复,Runtime 是 Objective-C 的運(yùn)行時(shí)系統(tǒng),它提供了一系列功能竖螃,包括動(dòng)態(tài)性淑廊、元數(shù)據(jù)、消息傳遞特咆、方法調(diào)用季惩、內(nèi)存管理等,使得 Objective-C 具有很強(qiáng)的靈活性和動(dòng)態(tài)性腻格。深入理解 Runtime 可以幫助開發(fā)者更好地理解 Objective-C 語言的運(yùn)行機(jī)制画拾,從而編寫出更加靈活和高效的代碼。