面試題的答案都是拋磚引玉,具體細節還得深入瞭解iOS底層原理
複製程式碼
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賦值
// 1.1 建立人
PTLPerson *p = [[PTLPerson alloc] init];
self.person = p;
// 1.2 建立狗
PTLDog *dog = [[PTLDog alloc] init];
// 1.3 將狗賦值給人
[p setValue:dog forKeyPath:@"dog"];
// 1.4 通過KVC給dog的weight屬性賦值
賦值時會自動找到人擁有的dog的weight屬性
[p setValue:@10.0 forKeyPath:@"dog.weight"];
NSLog(@"books = %@", [p valueForKeyPath:@"dog.weight"]);
[dog print];
複製程式碼
7.2、 KVC取值
NSMutableArray *tempM = [NSMutableArray array];
// 2.1 kvc取出出陣列books中price的值
for (PTLBook *book in [p valueForKeyPath:@"books"]) {
[tempM addObject:[book valueForKeyPath:@"price"]];
}
NSLog(@"%@", tempM);
// 2.2 kvc取出陣列中price的最大值
NSLog(@"Max = %@", [[p valueForKeyPath:@"books"] valueForKeyPath:@"@max.price"]);
複製程式碼
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為子類的觀察者屬性重寫呼叫存取方法的工作原理在程式碼中相當於:
-(void)setName:(NSString *)newName
{
[self willChangeValueForKey:@"name"]; //KVO在呼叫存取方法之前總呼叫
[super setValue:newName forKey:@"name"]; //呼叫父類的存取方法
[self didChangeValueForKey:@"name"]; //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總結
- 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找父類