收藏的一些iOS開發(fā)基礎(chǔ)面試題

1.如何理解RunLoop

Runloop(運(yùn)行循環(huán))是iOS和macOS中的一個核心概念,它負(fù)責(zé)管理事件和計時器,以確保應(yīng)用程序能夠在正確的時間響應(yīng)用戶的輸入,并在不占用過多資源的情況下保持活動狀態(tài)。
在一個iOS或macOS應(yīng)用程序中,有一個主線程,所有的用戶界面更新、網(wǎng)絡(luò)請求、定時器、事件處理等都是在該線程中執(zhí)行的。而 Runloop 就是這個線程中的一個對象,它不斷地監(jiān)聽系統(tǒng)事件,如觸摸事件、定時器事件等,一旦有事件發(fā)生,就會通知相應(yīng)的處理方法來處理該事件。
Runloop 監(jiān)聽的事件主要分為兩種:輸入源(Input Source)和定時源(Timer Source)。輸入源包括觸摸事件、鍵盤事件、鼠標(biāo)事件等,定時源則是基于時間的觸發(fā)器,如 NSTimer、CADisplayLink 等。當(dāng)事件發(fā)生時,Runloop 會將事件分發(fā)給相應(yīng)的處理方法,并在事件處理完成后繼續(xù)監(jiān)聽事件。
Runloop 的好處在于它可以讓應(yīng)用程序在等待事件時保持活動狀態(tài),同時也能夠在沒有事件時降低資源占用。因此,Runloop 是 iOS 和 macOS 應(yīng)用程序能夠保持流暢和響應(yīng)的關(guān)鍵之一。

2.如何理解RunTime

Runtime(運(yùn)行時)是指程序在運(yùn)行期間的行為,即代碼被編譯成二進(jìn)制文件之后,在運(yùn)行時期間的行為。在編譯時,我們可以預(yù)測代碼的行為,但是在運(yùn)行時,程序的行為往往會有一些意外情況,比如動態(tài)創(chuàng)建對象、動態(tài)綁定方法等。
在Objective-C中,Runtime是一個重要的概念,它為Objective-C提供了一些高級特性,如消息傳遞、動態(tài)綁定和反射等。Objective-C的對象都是在運(yùn)行時創(chuàng)建的,它們的屬性和方法也是在運(yùn)行時添加的。因此,Objective-C程序的運(yùn)行時環(huán)境是非常動態(tài)的。
使用Runtime可以做很多有用的事情,例如:
動態(tài)創(chuàng)建類、對象和方法
攔截方法調(diào)用并修改方法實(shí)現(xiàn)
動態(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中的兩個重要機(jī)制,它們之間有一定的聯(lián)系,同時也有一定的區(qū)別。
KVC是一種通過字符串訪問對象屬性的機(jī)制,它允許我們使用字符串來獲取和設(shè)置對象的屬性,而不需要顯式地調(diào)用對象的getter和setter方法。KVC的核心是通過key來訪問對象的屬性,即通過字符串訪問對象的屬性。
KVO則是一種觀察者模式的實(shí)現(xiàn),它允許一個對象監(jiān)聽另一個對象的某個屬性的變化,并在該屬性發(fā)生變化時自動得到通知。在iOS開發(fā)中,我們通常使用KVO來實(shí)現(xiàn)對于UI控件的響應(yīng)、數(shù)據(jù)模型的觀察等場景。
KVC和KVO之間的聯(lián)系在于,KVO的實(shí)現(xiàn)需要使用到KVC。當(dāng)我們對一個對象的屬性進(jìn)行觀察時,KVO會自動為被觀察的對象動態(tài)生成一個子類,并在該子類中重寫被觀察屬性的setter方法。在該setter方法中,KVO會添加一些通知的邏輯,以便在屬性值發(fā)生變化時發(fā)送通知。
因此,當(dāng)我們使用KVO時,可以通過KVC來訪問被觀察對象的屬性值。同時,KVC也可以在不使用KVO的情況下,直接訪問對象的屬性,從而使我們的代碼更加簡潔和易讀。
需要注意的是,KVC和KVO雖然在一些場景下會同時使用,但是它們是兩個不同的機(jī)制,應(yīng)該根據(jù)具體的需求來選擇是否使用它們。

4.iOS的事件傳遞過程

事件從UIApplication開始,按照視圖層次結(jié)構(gòu)自頂向下地傳遞。在這個過程中,系統(tǒng)會根據(jù)事件類型和響應(yīng)者鏈來決定將事件發(fā)送給哪個視圖或控件。當(dāng)事件傳遞到某個視圖或控件時,該視圖或控件會嘗試處理該事件。如果該視圖或控件不能處理該事件,則會將該事件轉(zhuǎn)發(fā)給下一個響應(yīng)者。這個過程會一直持續(xù)到某個響應(yīng)者處理了該事件,或者事件傳遞到UIApplication時結(jié)束。
當(dāng)事件處理完成后,系統(tǒng)會對事件做出最終的響應(yīng)。例如,在觸摸事件中,系統(tǒng)會根據(jù)手指的狀態(tài)來判斷該事件是否是一個單擊、雙擊、長按等手勢。同時,系統(tǒng)還會在事件結(jié)束時對相關(guān)的UI元素進(jìn)行更新和重繪。
在事件傳遞過程中,可以通過重寫視圖或控件的響應(yīng)方法來改變事件的傳遞和處理行為。例如,可以重寫hitTest:withEvent:方法來指定事件的響應(yīng)對象;重寫pointInside:withEvent:方法來指定響應(yīng)區(qū)域;重寫touchesBegan:withEvent:方法來處理觸摸事件等。
總之,在iOS中,事件傳遞過程是一個復(fù)雜而又精細(xì)的機(jī)制。了解事件傳遞過程對于優(yōu)化應(yīng)用程序的響應(yīng)速度和用戶體驗(yàn)有著重要的意義。

5.CALayer與UIView的關(guān)系

CALayer是UIView的底層實(shí)現(xiàn),可以看作是UIView的圖層。每個UIView都有一個對應(yīng)的CALayer對象,它負(fù)責(zé)管理UIView的內(nèi)容和顯示。UIView提供了一些高級的界面控制功能,例如響應(yīng)用戶交互事件、布局管理、自動繪制等,而CALayer則負(fù)責(zé)渲染UIView的內(nèi)容,包括繪制、變換、動畫等。

UIView和CALayer的關(guān)系可以用以下兩種方式來理解:

  • UIView是CALayer的外觀:UIView提供了許多高級的界面控制功能,例如用戶交互、布局管理等,這些功能都建立在CALayer的基礎(chǔ)之上。因此,可以將UIView看作是CALayer的外觀,它通過對CALayer的屬性進(jìn)行設(shè)置,來控制圖層的顯示和行為。
  • UIView是CALayer的管理者:UIView負(fù)責(zé)管理CALayer對象的創(chuàng)建、銷毀、布局等,它維護(hù)了一個CALayer的層級結(jié)構(gòu),并負(fù)責(zé)對圖層的屬性進(jìn)行設(shè)置。UIView還提供了一些高級的功能,例如自動繪制、動畫效果等,這些功能都是基于CALayer實(shí)現(xiàn)的。

因此,可以說CALayer是UIView的底層實(shí)現(xiàn),UIView提供了對CALayer的封裝,使得開發(fā)者可以更加方便地使用和管理圖層。在開發(fā)中,如果需要實(shí)現(xiàn)一些高級的界面效果,例如復(fù)雜的動畫、圖層蒙版等,就需要直接使用CALayer來實(shí)現(xiàn)。

6.iOS中為什么代理需要用weak修飾
  • 避免循環(huán)引用:如果代理對象使用strong來修飾,那么代理對象就會對委托對象進(jìn)行強(qiáng)引用,而委托對象又會對代理對象進(jìn)行引用,從而形成循環(huán)引用。這樣就會導(dǎo)致對象無法釋放,造成內(nèi)存泄漏。因此,使用weak來修飾代理對象可以避免循環(huán)引用問題。
  • 被代理對象通常具有更長的生命周期:在大多數(shù)情況下,代理對象是由被代理對象持有的,因此被代理對象通常具有更長的生命周期。如果使用strong來修飾代理對象,那么代理對象就會一直存在于內(nèi)存中,即使被代理對象已經(jīng)不存在了。這樣就會造成內(nèi)存浪費(fèi)。

總之,使用weak來修飾代理對象可以避免循環(huán)引用問題,同時也更符合代理對象與被代理對象之間的實(shí)際關(guān)系。

7.Block為什么要用copy修飾

Block在定義時會捕獲作用域中的變量和對象,并將其保存在一個數(shù)據(jù)結(jié)構(gòu)中,以便在后續(xù)的調(diào)用中使用。
由于Block可能在定義時保存了局部變量或?qū)ο蟮囊茫绻贐lock在定義后延遲執(zhí)行時,這些局部變量或?qū)ο笠呀?jīng)被銷毀,那么在Block執(zhí)行時就會訪問到已經(jīng)釋放的內(nèi)存空間,導(dǎo)致程序崩潰。因此,為了避免這種問題,Block需要使用copy修飾。
使用copy修飾Block會將其從棧區(qū)復(fù)制到堆區(qū),這樣就能保證在Block執(zhí)行時,其所依賴的對象和變量的引用不會因?yàn)樽饔糜虻母淖兌?。具體來說,當(dāng)Block被復(fù)制到堆區(qū)時,其所依賴的對象和變量也會被一并復(fù)制到堆區(qū),這樣就能保證在Block執(zhí)行時,其所依賴的對象和變量的引用是有效的。
需要注意的是,在ARC(Automatic Reference Counting)環(huán)境下,如果Block沒有捕獲任何對象,則可以使用strong來修飾,因?yàn)檫@種情況下Block不會持有任何對象的引用。但是如果Block捕獲了對象或變量,則仍然需要使用copy來修飾,以確保Block的正確性。

block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū);在 ARC 中寫不寫都行。
對于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進(jìn)行了 copy 操作。如果不寫 copy ,該類的調(diào)用者有可能會忘記或者根本不知道“編譯器會自動對 block 進(jìn)行了 copy 操作”,他們有可能會在調(diào)用之前自行拷貝屬性值。這種操作多余而低效。

8.什么是Block

Block是一種用于封裝一段代碼的數(shù)據(jù)類型。Block實(shí)際上是一個匿名函數(shù),它可以捕獲一些變量和常量,并將它們封裝在一起,形成一個可以在需要時執(zhí)行的代碼塊。Block可以被當(dāng)作一個對象來使用,它可以作為方法參數(shù)、成員變量、局部變量、數(shù)組元素等等。Block中捕獲的變量和常量被保存在Block中,可以在Block執(zhí)行時使用。

在使用Block時,需要注意以下幾點(diǎn):

  • Block中捕獲的變量需要在Block執(zhí)行時仍然存在。如果Block中捕獲的變量是局部變量,那么需要使用__block修飾符來使其在Block執(zhí)行時仍然存在。
  • Block中使用的變量需要正確地被引用。如果Block中使用了對象,那么需要使用strong或weak修飾符來指定對該對象的引用方式。如果Block中使用了基本數(shù)據(jù)類型的變量,則不需要使用修飾符。
  • Block可以作為參數(shù)傳遞給方法或函數(shù),也可以在方法或函數(shù)中定義。如果Block作為參數(shù)傳遞給方法或函數(shù),則需要使用^符號定義。

總之,Block是一種非常強(qiáng)大和靈活的數(shù)據(jù)類型,它可以用于封裝一段代碼,并將其當(dāng)做一個對象來使用。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ù)器注冊。
  • 注冊過程中,iOS應(yīng)用程序會生成一個設(shè)備令牌(Device Token),并將該令牌發(fā)送給APNs服務(wù)器。設(shè)備令牌是一個唯一標(biāo)識符,用于標(biāo)識該設(shè)備的APNs推送服務(wù)。
  • APNs服務(wù)器會將設(shè)備令牌保存在其數(shù)據(jù)庫中,并為該設(shè)備生成一個注冊ID(Registration ID),用于標(biāo)識該設(shè)備在APNs服務(wù)器上的注冊信息。
    當(dāng)開發(fā)者需要向某個設(shè)備發(fā)送推送通知時,需要將推送通知發(fā)送給APNs服務(wù)器。
  • APNs服務(wù)器會根據(jù)設(shè)備令牌和注冊ID,將推送通知發(fā)送到對應(yīng)的設(shè)備上。
    接收到推送通知的設(shè)備會顯示通知內(nèi)容,并執(zhí)行相應(yīng)的操作。

在實(shí)際開發(fā)中,iOS應(yīng)用程序可以通過使用APNs SDK,來向APNs服務(wù)器注冊、發(fā)送推送通知等操作。具體來說,可以使用APNs SDK中提供的API,通過創(chuàng)建連接、發(fā)送請求等方式,來實(shí)現(xiàn)對APNs服務(wù)器的訪問和操作。同時,iOS應(yīng)用程序也需要提供一些必要的配置信息,如證書、證書密碼、設(shè)備令牌等等,以便與APNs服務(wù)器進(jìn)行通信

10.談?wù)剬?nèi)存管理的理解

在iOS中,主要采用了自動引用計數(shù)(Automatic Reference Counting,ARC)的機(jī)制來進(jìn)行內(nèi)存管理。ARC是一種編譯時自動插入內(nèi)存管理代碼的技術(shù),它可以自動地分析對象的引用關(guān)系,并在對象不再被使用時自動釋放對象所占用的內(nèi)存。ARC機(jī)制可以極大地簡化內(nèi)存管理的過程,減少了手動釋放內(nèi)存的工作量,提高了應(yīng)用程序的性能和穩(wěn)定性。

在使用ARC時,需要注意以下幾點(diǎn):

  • 對象的引用關(guān)系需要正確地維護(hù)。如果對象被多個變量引用,那么需要確保所有引用都正確地被處理。
  • 對象的循環(huán)引用需要避免。循環(huán)引用會導(dǎo)致對象無法被正確地釋放,從而導(dǎo)致內(nèi)存泄漏。
  • 對象的生命周期需要正確地管理。對象在不再被使用時應(yīng)該及時釋放,以便及時回收內(nèi)存資源。

除了ARC機(jī)制外,iOS還提供了一些手動管理內(nèi)存的方式,如MRC(Manual Reference Counting)和內(nèi)存池技術(shù)等。在使用這些技術(shù)時,需要更加謹(jǐn)慎地管理對象的引用關(guān)系和生命周期,以免出現(xiàn)內(nèi)存泄漏等問題。

11.什么是內(nèi)存池

內(nèi)存池是一種優(yōu)化內(nèi)存分配和管理的技術(shù),它可以提高內(nèi)存分配和釋放的效率,減少因頻繁申請和釋放內(nèi)存而帶來的性能損失。
內(nèi)存池的基本思想是在程序初始化時預(yù)先分配一定數(shù)量的內(nèi)存塊,并把它們存放在一個池子里面。當(dāng)需要申請內(nèi)存時,不再使用系統(tǒng)提供的malloc()或new運(yùn)算符來動態(tài)申請內(nèi)存,而是直接從內(nèi)存池中取出一個內(nèi)存塊進(jìn)行使用。當(dāng)不再需要這個內(nèi)存塊時,也不用立即將其釋放,而是將其放回內(nèi)存池中,以備后續(xù)使用。

內(nèi)存池的優(yōu)點(diǎn)主要有:

  • 減少內(nèi)存分配和釋放的次數(shù),降低內(nèi)存碎片的產(chǎn)生,提高內(nèi)存分配和釋放的效率。
  • 程序初始化時可以預(yù)先分配一定數(shù)量的內(nèi)存,避免在程序運(yùn)行過程中頻繁地進(jìn)行內(nèi)存分配和釋放,提高程序的性能。
  • 內(nèi)存池可以減少內(nèi)存申請和釋放帶來的鎖競爭,從而提高程序的并發(fā)性能。

在iOS開發(fā)中,內(nèi)存池的應(yīng)用比較廣泛,例如在網(wǎng)絡(luò)編程中使用的Socket連接池、數(shù)據(jù)庫連接池、圖片緩存池等。內(nèi)存池技術(shù)可以有效地優(yōu)化內(nèi)存管理,提高程序的性能和穩(wěn)定性,但需要注意合理地設(shè)計內(nèi)存池的大小和對象的生命周期,以避免因內(nèi)存池過大或過小而造成的資源浪費(fèi)或性能損失。

12.為什么要使用__weak
13.為什么Block使用了__block,就可以修改block內(nèi)部的變量

__block修飾符標(biāo)記后,block就會訪問標(biāo)記變量本身內(nèi)存地址,而未標(biāo)記對象則訪問截獲拷貝后的變量的內(nèi)存地址

14.iOS的啟動優(yōu)化都有哪些
15.闡述一下APP的啟動流程

1、系統(tǒng)先讀取App的可執(zhí)行文件(Mach-O文件),獲取到dyld(動態(tài)庫)的路徑,并加載dyld(動態(tài)庫鏈接程序)。
2、dyld去初始化運(yùn)行環(huán)境、開啟緩存策略(冷熱啟動)、加載依賴庫(讀取文件、驗(yàn)證、注冊到系統(tǒng)核心)、我們的可執(zhí)行文件、鏈接依賴庫,并調(diào)用每個依賴庫的初始化方法。
3、在上一步runtime被初始化,當(dāng)所有的依賴庫初始化后,程序可執(zhí)行文件進(jìn)行初始化,這個時候runtime會對項目中的所有類進(jìn)行類結(jié)構(gòu)初始化,然后調(diào)用所有類的+load方法。

1、runtime初始化方法 _objc_init 中最后注冊了兩個通知:
map_images: 主要是在鏡像加載進(jìn)內(nèi)容后對其二進(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方法并不會覆蓋主類以及其他同主類的分類里的load 方法實(shí)現(xiàn)了。)
2、runtime 調(diào)用項目中所有的load方法時,所有的類的結(jié)構(gòu)已經(jīng)初始化了,此時在load方法中可以使用任何類創(chuàng)建實(shí)例并給他們發(fā)送消息。

4、最后dyld返回main函數(shù)地址,main函數(shù)被調(diào)用。dyld會緩存上一次把信息加載內(nèi)存的緩存,所以第二次比第一次啟動快一點(diǎn)。

pre-main
初始化空間,加載鏡像,載入動態(tài)庫鏈接器,鏈接動態(tài)庫,objcsetup,callloadmethod,
動態(tài)庫越多,啟動越慢,+load方法多也會影響
優(yōu)化方向:靜態(tài)庫,cocoapods :linkage => :static指令 將所有的動態(tài)庫轉(zhuǎn)為靜態(tài)庫。 要注意資源獲取的方式問題。
合并動態(tài)庫:cocoapods-pod-merge 支持合并動態(tài)庫,要注意import方式會改變,作用域前綴要加上
main
main函數(shù)以后就是盡量減少第一個頁面展示前的工作,有些組件庫自己的工作可以派發(fā)到庫自己處理,或者異步處理
啟動項熱拔插

main函數(shù)之前優(yōu)化

啟動的第一步是加載動態(tài)庫,加載系統(tǒng)的動態(tài)庫使很快的,因?yàn)榭梢跃彺?,而加載內(nèi)嵌的動態(tài)庫速度較慢。所以,提高這一步的效率的關(guān)鍵是:減少動態(tài)庫的數(shù)量。
Rebase和Bind都是為了解決指針引用的問題。對于Objective C開發(fā)來說,主要的時間消耗在Class/Method的符號加載上,所以常見的優(yōu)化方案是:
合并Category和功能類似的類。比如:UIView+Frame,UIView+AutoLayout…合并為一個
刪除無用的方法和類。

16.KVC的原理

1.KVC是基于runtime機(jī)制實(shí)現(xiàn)的
2、可以訪問私有成員變量、可以間接修改私有變量的值

KVC鍵值查找原理
setValue:forKey:搜索方式
1、首先搜索setKey:方法.(key指成員變量名, 首字母大寫)
2、上面的setter方法沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey,key, iskey的順序搜索成員名。(這個類方法是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)建值類型, 會做NSNumber的轉(zhuǎn)換.
2、上面的getter沒找到, 查找countOfKey, objectInKeyAtindex, KeyAtindexes格式的方法. 如果countOfKey和另外兩個方法中的一個找到, 那么就會返回一個可以響應(yīng)NSArray所有方法的代理集合的NSArray消息方法.
3、還沒找到, 查找countOfKey, enumeratorOfKey, memberOfKey格式的方法. 如果這三個方法都找到, 那么就返回一個可以響應(yīng)NSSet所有方法的代理集合.
4、還是沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey, key, iskey的順序搜索成員名.
5、再沒找到, 調(diào)用valueForUndefinedKey.

17.KVO原理

1.KVO是基于runtime機(jī)制實(shí)現(xiàn)的
2.當(dāng)某個類的屬性對象第一次被觀察時,系統(tǒng)就會在運(yùn)行期動態(tài)地創(chuàng)建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制
3.如果原類為Person,那么生成的派生類名為NSKVONotifying_Person
4.每個類對象中都有一個isa指針指向當(dāng)前類,當(dāng)一個類對象的第一次被觀察,那么系統(tǒng)會偷偷將isa指針指向動態(tài)生成的派生類,從而在給被監(jiān)控屬性賦值時執(zhí)行的是派生類的setter方法
5.鍵值觀察通知依賴于NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個被觀察屬性發(fā)生改變之前, willChangeValueForKey:一定會被調(diào)用,這就 會記錄舊的值。而當(dāng)改變發(fā)生后,didChangeValueForKey:會被調(diào)用,繼而 observeValueForKey:ofObject:change:context: 也會被調(diào)用。

深入

1.Apple 使用了 isa 混寫(isa-swizzling)來實(shí)現(xiàn) KVO 。當(dāng)觀察對象A時,KVO機(jī)制動態(tài)創(chuàng)建一個新的名為:?NSKVONotifying_A的新類,該類繼承自對象A的本類,且KVO為NSKVONotifying_A重寫觀察屬性的setter?方法,setter?方法會負(fù)責(zé)在調(diào)用原?setter?方法之前和之后,通知所有觀察對象屬性值的更改情況。
2.NSKVONotifying_A類剖析:在這個過程,被觀察對象的 isa 指針從指向原來的A類,被KVO機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_A類,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽;
3.所以當(dāng)我們從應(yīng)用層面上看來,完全沒有意識到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對KVO的底層實(shí)現(xiàn)過程,讓我們誤以為還是原來的類。但是此時如果我們創(chuàng)建一個新的名為“NSKVONotifying_A”的類(),就會發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊KVO的那段代碼時程序就崩潰,因?yàn)橄到y(tǒng)在注冊監(jiān)聽的時候動態(tài)創(chuàng)建了名為NSKVONotifying_A的中間類,并指向這個中間類了。
4.(isa 指針的作用:每個對象都有isa 指針,指向該對象的類,它告訴 Runtime 系統(tǒng)這個對象的類是什么。所以對象注冊為觀察者時,isa指針指向新子類,那么這個被觀察的對象就神奇地變成新子類的對象(或?qū)嵗┝恕#?因而在該對象上對 setter 的調(diào)用就會調(diào)用已重寫的 setter,從而激活鍵值通知機(jī)制。
5.子類setter方法剖析:KVO的鍵值觀察通知依賴于 NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數(shù)值的前后分別調(diào)用2個方法: 被觀察屬性發(fā)生改變之前,willChangeValueForKey:被調(diào)用,通知系統(tǒng)該 keyPath?的屬性值即將變更;當(dāng)改變發(fā)生后, didChangeValueForKey: 被調(diào)用,通知系統(tǒng)該 keyPath?的屬性值已經(jīng)變更;之后,?observeValueForKey:ofObject:change:context: 也會被調(diào)用。且重寫觀察屬性的setter?方法這種繼承方式的注入是在運(yùn)行時而不是編譯時實(shí)現(xiàn)的。

18.category為什么不能添加屬性?

category 它是在運(yùn)行期決議的,因?yàn)樵谶\(yùn)行期,對象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會破壞類的內(nèi)部布局,這對編譯型語言來說是災(zāi)難性的。
extension看起來很像一個匿名的category,但是extension和有名字的category幾乎完全是兩個東西。 extension在編譯期決議,它就是類的一部分,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個完整的類,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡。extension一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension。
但是category則完全不一樣,它是在運(yùn)行期決議的。
就category和extension的區(qū)別來看,我們可以推導(dǎo)出一個明顯的事實(shí),extension可以添加實(shí)例變量,而category是無法添加實(shí)例變量的。

那為什么 使用Runtime技術(shù)中的關(guān)聯(lián)對象可以為類別添加屬性。
其原因是:關(guān)聯(lián)對象都由AssociationsManager管理,AssociationsManager里面是由一個靜態(tài)AssociationsHashMap來存儲所有的關(guān)聯(lián)對象的。這相當(dāng)于把所有對象的關(guān)聯(lián)對象都存在一個全局map里面。而map的的key是這個對象的指針地址(任意兩個不同對象的指針地址一定是不同的),而這個map的value又是另外一個AssociationsHashMap,里面保存了關(guān)聯(lián)對象的kv對。

如合清理關(guān)聯(lián)對象?
runtime的銷毀對象函數(shù)objc_destructInstance里面會判斷這個對象有沒有關(guān)聯(lián)對象,如果有,會調(diào)用_object_remove_assocations做關(guān)聯(lián)對象的清理工作。

19.類擴(kuò)展和分類的區(qū)別
  • 分類不能添加成員變量【雖然可以添加屬性 但是一旦調(diào)用就會報方法找不到的錯誤】 (可以通過runtime給分類間接添加成員變量),而類擴(kuò)展可以添加成員變量;
  • 分類中的屬性不會自動實(shí)現(xiàn)set方法和get方法,而類擴(kuò)展中的屬性再轉(zhuǎn)為底層時是可以自動實(shí)現(xiàn)set、get方法
  • 類擴(kuò)展中添加的新方法,不實(shí)現(xiàn)會報警告。categorygory中定義了方法不實(shí)現(xiàn)則沒有這個問題
  • 類擴(kuò)展可以定義在.m文件中,這種擴(kuò)展方式中定義的變量都是私有的,也可以定義在.h文件中,這樣定義的代碼就是共有的,類擴(kuò)展在.m文件中聲明私有方法是非常好的方式。
  • 類擴(kuò)展不能像分類那樣擁有獨(dú)立的實(shí)現(xiàn)部分(@implementation部分),也就是說,類擴(kuò)展所聲明的方法必須依托對應(yīng)類的實(shí)現(xiàn)部分來實(shí)現(xiàn)。
20.App的啟動流程
main函數(shù)之前

系統(tǒng)將App的可執(zhí)行文件(Mach-O文件)和dyld加載到內(nèi)存,由dyld進(jìn)行動態(tài)鏈接。

  • 設(shè)置相關(guān)環(huán)境變量
    根據(jù)環(huán)境變量設(shè)置相應(yīng)的值以及獲取當(dāng)前運(yùn)行架構(gòu)。例如配置環(huán)境變量打印啟動流程耗時: DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。

  • 加載共享緩存庫
    加載動態(tài)共享緩存庫到動態(tài)庫共享緩存區(qū),例如UIKit、CoreFoundation等官方庫。

  • 加載動態(tài)庫
    把所有的可執(zhí)行文件所依賴的動態(tài)庫遞歸加載到內(nèi)存中。

  • rebase和binding
    iOS采用ASLR技術(shù)(地址空間布局隨機(jī)化),加載App的內(nèi)存地址是隨機(jī)的,rebase會根據(jù)隨機(jī)的偏移量對原來的地址做重定向。
    binding進(jìn)行符號綁定。指向image外部動態(tài)庫的指針被符號(symbol)綁定。dyld需要去符號表里查找,找到對應(yīng)的實(shí)現(xiàn)。

  • Objc setup
    (1)注冊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)存時 , 會觸發(fā)該函數(shù).
load_images : dyld 初始化 image 會觸發(fā)該方法. ( 我們所熟知的 load 方法也是在此處調(diào)用 ) .
dyld在初始化其他動態(tài)庫之前,會最先初始化系統(tǒng)庫libsystem,運(yùn)行Runtime。系統(tǒng)庫libsystem初始化完成后,就會初始化其他動態(tài)庫,然后由Runtime調(diào)用map_images來讀取類、方法、協(xié)議以及分類并存儲到對應(yīng)的表中(注意:分類并不是直接存,而是通過attachLists方法把分類的數(shù)據(jù)添加到類里面),然后Runtime會繼續(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方法時,做了遞歸處理,會先調(diào)用父類的load,然后再調(diào)用子類的load,所有類的load方法調(diào)用完成后,才會開始處理所有類的分類,分類的處理順序取決于Mach-O頭文件,和類的順序沒有直接關(guān)系。先后順序即:父類->子類->所有類的分類。

main函數(shù)階段

main() 會調(diào)用 UIApplicationMain(),直至application:didFinishLaunchingWithOptions:執(zhí)行完畢,整個啟動流程就完成了。

21.談?wù)剬卫J降睦斫?/h5>

單例模式是一種設(shè)計模式,它確保一個類只有一個實(shí)例,并提供一個全局訪問點(diǎn)來訪問該實(shí)例。單例模式通常用于需要全局唯一對象的場景,比如配置管理、資源管理、日志記錄等。

(1)唯一實(shí)例: 單例模式確保一個類只有一個實(shí)例存在,無論何時何地,對該類的實(shí)例的訪問都只會返回同一個對象。這樣可以避免創(chuàng)建多個對象造成資源的浪費(fèi),同時保證對象的唯一性。
(2)全局訪問點(diǎn): 單例模式提供了一個全局訪問點(diǎn)來訪問唯一的實(shí)例,使得其他對象可以通過該訪問點(diǎn)獲取單例對象的引用。這樣可以方便地在程序的任何地方使用單例對象,而不需要顯式地創(chuàng)建對象或傳遞對象的引用。
(3)延遲實(shí)例化: 單例模式通常采用延遲實(shí)例化的方式來創(chuàng)建單例對象,即在第一次訪問單例對象時才創(chuàng)建對象。這樣可以節(jié)省資源,避免在程序啟動時就創(chuàng)建對象,提高程序的啟動速度。
(4)線程安全性: 在多線程環(huán)境下,單例模式需要考慮線程安全性的問題,確保在多線程環(huán)境下也能正確地創(chuàng)建和訪問單例對象。常見的線程安全實(shí)現(xiàn)方式包括使用同步鎖、靜態(tài)變量初始化、延遲初始化等。
(5)生命周期管理: 單例模式通常是全局存在的,因此需要考慮對象的生命周期管理,確保單例對象在合適的時機(jī)被正確地釋放和清理。這樣可以避免內(nèi)存泄漏和資源泄漏問題。
(6)全局狀態(tài)共享: 單例模式可以用來實(shí)現(xiàn)全局狀態(tài)共享,多個對象可以共享同一個單例對象的狀態(tài),從而實(shí)現(xiàn)數(shù)據(jù)共享和通信。

總的來說,單例模式是一種簡單而又實(shí)用的設(shè)計模式,它提供了一種方便的方式來管理全局唯一對象,并確保對象的唯一性、延遲實(shí)例化、線程安全性等特性,適用于許多需要全局唯一對象的場景。但是在使用單例模式時需要注意線程安全性和生命周期管理等問題,以確保單例對象的正確使用和管理。

22.談一談對runtime的理解

(1)動態(tài)性: Runtime 是 Objective-C 的運(yùn)行時系統(tǒng),它允許在程序運(yùn)行時動態(tài)地創(chuàng)建類、修改類的結(jié)構(gòu)、調(diào)用方法等。這種動態(tài)性使得 Objective-C 具有很強(qiáng)的靈活性,可以實(shí)現(xiàn)一些在靜態(tài)語言中難以實(shí)現(xiàn)的功能,比如動態(tài)派發(fā)、消息轉(zhuǎn)發(fā)等。
(2)元數(shù)據(jù): Runtime 中存儲了關(guān)于類、對象、方法等的元數(shù)據(jù)信息,包括類的名稱、父類、實(shí)例變量、方法列表等。通過這些元數(shù)據(jù)信息,可以在運(yùn)行時對類和對象進(jìn)行操作,比如動態(tài)創(chuàng)建類和對象、動態(tài)添加方法等。
(3)消息傳遞: 在 Objective-C 中,方法調(diào)用是通過消息傳遞的方式實(shí)現(xiàn)的,即發(fā)送一個消息給對象,對象根據(jù)消息選擇合適的方法來執(zhí)行。Runtime 提供了消息傳遞的機(jī)制,包括消息發(fā)送、方法解析、消息轉(zhuǎn)發(fā)等,使得 Objective-C 具有動態(tài)派發(fā)的特性。
(4)方法調(diào)用: Runtime 提供了一系列方法來實(shí)現(xiàn)方法的調(diào)用,包括實(shí)例方法調(diào)用、類方法調(diào)用、動態(tài)方法解析、方法交換等。通過這些方法,可以在運(yùn)行時動態(tài)地調(diào)用方法,甚至可以修改方法的實(shí)現(xiàn)。
(5)內(nèi)存管理: Runtime 還提供了一些內(nèi)存管理的功能,包括自動引用計數(shù)(ARC)和自動釋放池(Autorelease Pool)。通過這些功能,可以在運(yùn)行時對對象的內(nèi)存進(jìn)行管理,確保對象的引用計數(shù)正確,避免內(nèi)存泄漏和野指針問題。

綜上所述,Runtime 是 Objective-C 的運(yùn)行時系統(tǒng),它提供了一系列功能,包括動態(tài)性、元數(shù)據(jù)、消息傳遞、方法調(diào)用、內(nèi)存管理等,使得 Objective-C 具有很強(qiáng)的靈活性和動態(tài)性。深入理解 Runtime 可以幫助開發(fā)者更好地理解 Objective-C 語言的運(yùn)行機(jī)制,從而編寫出更加靈活和高效的代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容