iOS底層面試題---OC語法部分
11-23
1、一個NSObject對象佔用多少內存?
- 系統分配了16個位元組給NSObject對象(通過malloc_size函數獲得)
- 但NSObject對象內部只使用了8個位元組的空間(64bit環境下,可以通過class_getInstanceSize函數獲得)
2、對象的isa指針指向哪裡?
- instance對象的isa指向class對象
- class對象的isa指向meta-class對象
- meta-class對象的isa指向基類的meta-class對象
3、OC的類信息存放在哪裡?
- 對象方法、屬性、成員變數、協議信息,存放在class對象中
- 類方法,存放在meta-class對象中
- 成員變數的具體值,存放在instance對象中
4、iOS用什麼方式實現對一個對象的KVO?(KVO的本質是什麼?)
- 利用RuntimeAPI動態生成一個子類,並且讓instance對象的isa指向這個全新的子類
- 當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueAndNotify函數
√ willChangeValueForKey:
√ 父類原來的setter√ didChangeValueForKey:√ 內部會觸發監聽器(Oberser)的監聽方法( observeValueForKeyPath:ofObject:change:context:)
5、如何手動觸發KVO?
- 手動調用willChangeValueForKey:和didChangeValueForKey:
6、直接修改成員變數或屬性會觸發KVO么?
- 修改成員變數不會觸發KVO
- 修改屬性會觸發KVO
7、KVC的賦值和取值過程是怎樣的?原理是什麼?
7.1、KVC賦值7.2、 KVC取值7.3、 原理- KVO 是 Objective-C 對觀察者設計模式的一種實現,另外一種是:通知機制(notification)
KVO提供一種機制,指定一個被觀察對象(例如A類),當對象某個屬性(例如A中的字元串name)發生更改時,對象會獲得通知,並作出相應處理
在MVC設計架構下的項目,KVO機制很適合實現mode模型和controller之間的通訊。例如:代碼中,在模型類A創建屬性數據,在控制器中創建觀察者,一旦屬性數據發生改變就收到觀察者收到通知,通過KVO再在控制器使用回調方法處理實現視圖B的更新;(本文中的應用就是這樣的例子.) - KVO在Apple中的API文檔如下:Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …KVO 的實現依賴於 Objective-C 強大的 Runtime【 ,從以上Apple 的文檔可以看出蘋果對於KVO機制的實現是一筆帶過,而具體的細節沒有過多的描述,但是我們可以通過Runtime的所提供的方法去探索關於KVO機制的底層實現原理.
- 當觀察某對象 A 時,KVO 機制動態創建一個對象A當前類的子類,並為這個新的子類重寫了被觀察屬性 keyPath 的 setter 方法。setter 方法隨後負責通知觀察對象屬性的改變狀況。
- Apple 使用了 isa 混寫(isa-swizzling)來實現 KVO 。當觀察對象A時,KVO機制動態創建一個新的名為:NSKVONotifying_A 的新類,該類繼承自對象A的本類,且 KVO 為 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會負責在調用原 setter 方法之前和之後,通知所有觀察對象屬性值的更改情況。
- NSKVONotifying_A類剖析:在這個過程,被觀察對象的 isa 指針從指向原來的A類,被KVO機制修改為指向系統新創建的子類 NSKVONotifying_A類,來實現當前類屬性值改變的監聽;所以當我們從應用層面上看來,完全沒有意識到有新的類出現,這是系統「隱瞞」了對KVO的底層實現過程,讓我們誤以為還是原來的類。但是此時如果我們創建一個新的名為「NSKVONotifying_A」的類(),就會發現系統運行到註冊KVO的那段代碼時程序就崩潰,因為系統在註冊監聽的時候動態創建了名為NSKVONotifying_A的中間類,並指向這個中間類了。因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。
- 子類setter方法剖析:KVO的鍵值觀察通知依賴於 NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數值的前後分別調用2個方法:被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之後observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的setter 方法這種繼承方式的注入是在運行時而不是編譯時實現的。KVO為子類的觀察者屬性重寫調用存取方法的工作原理在代碼中相當於:
8、Category的實現原理?
- Category編譯之後的底層結構是struct category_t,裡面存儲著分類的對象方法、類方法、屬性、協議信息
- 在程序運行的時候,runtime會將Category的數據,合併到類信息中(類對象、元類對象中)
9、Category和Class Extension的區別是什麼?
- Class Extension在編譯的時候,它的數據就已經包含在類信息中
- Category是在運行時,才會將數據合併到類信息中
10、Category中有load方法嗎?load方法是什麼時候調用的?load 方法能繼承嗎?
- 有load方法
- load方法在runtime載入類、分類的時候調用
- load方法可以繼承,但是一般情況下不會主動去調用load方法,都是讓系統自動調用
11、+load、+initialize方法的區別什麼?它們在category中的調用的順序?以及出現繼承時他們之間的調用過程?
+load- +load方法會在runtime載入類、分類時調用
- 每個類、分類的+load,在程序運行過程中只調用一次
- 調用順序:
- 1、先調用類的+load√ 按照編譯先後順序調用(先編譯,先調用)√ 調用子類的+load之前會先調用父類的+load
- 2、再調用分類的+load√ 按照編譯先後順序調用(先編譯,先調用)
+initialize
- +initialize方法會在類第一次接收到消息時調用
- 調用順序
1、先調用父類的+initialize,再調用子類的+initialize
2、(先初始化父類,再初始化子類,每個類只會初始化1次) - +initialize和+load的很大區別是,+initialize是通過objc_msgSend進行調用的,所以有以下特點√ 如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次)√ 如果分類實現了+initialize,就覆蓋類本身的+initialize調用
12、Category能否添加成員變數?如果可以,如何給Category添加成員變數?
- 默認情況下,因為分類底層結構的限制,不能添加成員變數到分類中。但可以通過關聯對象來間接實現
13、block的原理是怎樣的?本質是什麼?
- block本質上也是一個OC對象,它內部也有個isa指針
- 封裝了函數調用以及調用環境的OC對象
14、__block的作用是什麼?
- __block說明符類似static、auto、register一樣,只要觀察到該變數被 block 所持有,就將「外部變數」在棧中的內存地址放到了堆中。 進而在block內部也可以修改外部變數的值。
- __block可以用於解決block內部無法修改auto變數值的問題
- __block不能修飾全局變數、靜態變數(static)
- 編譯器會將__block變數包裝成一個對象
15、block的屬性修飾詞為什麼是copy?使用block有哪些使用注意?
- block一旦沒有進行copy操作,就不會在堆上
- 使用注意:循環引用問題
16、block在修改NSMutableArray,需不需要添加__block?
- 不需要
- 當變數是一個指針的時候,block里只是複製了一份這個指針,兩個指針指向同一個地址。所以,在block裡面對指針指向內容做的修改,在block外面也一樣生效。
17、說說isa指針?
- instance的isa指向class,當調用對象方法時,通過instance的isa找到class,最後找到對象方法的實現進行調用
- class的isa指向meta-class,當調用類方法時,通過class的isa找到meta-class,最後找到類方法的實現進行調用
- 當Student的instance對象要調用Person的對象方法時(Student繼承自Person),會先通過isa找到Student的class,然後通過superclass找到Person的class,最後找到對象方法的實現進行調用
- 當Student的class要調用Person的類方法時(Student繼承自Person),會先通過isa找到Student的meta-class,然後通過superclass找到Person的meta-class,最後找到類方法的實現進行調用
- 在arm64架構之前,isa就是一個普通的指針,存儲著Class、Meta-Class對象的內存地址
- 從arm64架構開始,對isa進行了優化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息
- 從64bit開始,isa需要進行一次位運算,才能計算出真實地址
18、isa、superclass總結
image- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基類的meta-class
- class的superclass指向父類的class如果沒有父類,superclass指針為nil
- meta-class的superclass指向父類的meta-class基類的meta-class的superclass指向基類的class
- instance調用對象方法的軌跡isa找到class,方法不存在,就通過superclass找父類
- class調用類方法的軌跡isa找meta-class,方法不存在,就通過superclass找父類
推薦閱讀: