本人參考GitHub《招聘一個(gè)靠譜的iOS》面試題參考答案(下)
51. KVC和KVO的keyPath一定是屬性么?
52. 如何關(guān)閉默認(rèn)的KVO的默認(rèn)實(shí)現(xiàn),并進(jìn)入自定義的KVO實(shí)現(xiàn)?
53. Apple用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?
54. IBOutlet連出來(lái)的視圖屬性為什么可以被設(shè)置成weak?
55. IB中User Defined Runtime Attributes如何使用?
56. 如何調(diào)試BAD_ACCESS錯(cuò)誤?
57. lldb(gdb)常用的調(diào)試命令?
51. KVC和KVO的keyPath一定是屬性么?
KVC支持實(shí)例變量,KVO只能手動(dòng)支持實(shí)例變量的KVO(見46. 如何手動(dòng)觸發(fā)一個(gè)value的KVO)。
52. 如何關(guān)閉默認(rèn)的KVO的默認(rèn)實(shí)現(xiàn),并進(jìn)入自定義的KVO實(shí)現(xiàn)?
系統(tǒng)是如何實(shí)現(xiàn)一個(gè)KVO的?
Mike Ash在2009年做過(guò)這方面的研究:當(dāng)觀察一個(gè)對(duì)象時(shí),一個(gè)新的類會(huì)動(dòng)態(tài)的被創(chuàng)建。這個(gè)類繼承自該對(duì)象的原本的類,并重寫了被觀察屬性的setter方法。重寫的setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法之前和之后,通知所有觀察對(duì)象,被觀察屬性的值的更改。最后把被觀察對(duì)象的isa指針(isa指針告訴Runtime系統(tǒng)這個(gè)對(duì)象的類是什么)指向新創(chuàng)建的子類,被觀察的對(duì)象就變成了新創(chuàng)建的子類的實(shí)例。Apple還重寫了-class方法,企圖欺騙我們這個(gè)類沒(méi)有改變,仍是原本的類。
具體實(shí)現(xiàn)參考:
53.Apple用什么方式實(shí)現(xiàn)一個(gè)對(duì)象的KVO?
Apple的文檔對(duì)KVO實(shí)現(xiàn)的描述:
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 the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class…
從Apple的文檔可以看出:Apple并不希望過(guò)多暴露KVO的實(shí)現(xiàn)細(xì)節(jié)。不過(guò),要是借助runtime提供的方法去深入挖掘,所有被掩蓋的細(xì)節(jié)都會(huì)原形畢露:
當(dāng)開發(fā)者觀察一個(gè)對(duì)象時(shí),一個(gè)新的類會(huì)被動(dòng)態(tài)創(chuàng)建。這個(gè)類繼承自該對(duì)象的原本的類,并重寫了被觀察屬性的setter方法。重寫的setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法之前和之后通知所有觀察者對(duì)象該屬性值的更改。最后通過(guò)
isa混些(isa-swizzling)把這個(gè)對(duì)象的isa指針(isa指針告訴runtime這個(gè)對(duì)象的類是什么)指向新創(chuàng)建的子類,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例。

詳細(xì)解釋:
鍵值觀察通知(KVO)依賴于NSObject的兩個(gè)方法:- (void)willChangeValueForKey:(NSString *)key和- (void)didChangeValueForKey:(NSString *)key。在一個(gè)被觀察的屬性發(fā)生改變之前,調(diào)用- (void)willChangeValueForKey:(NSString *)key記錄舊的值;當(dāng)改變發(fā)生只有,- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;會(huì)被調(diào)用,繼而- (void)didChangeValueForKey:(NSString *)key也會(huì)被調(diào)用。
比如調(diào)用setNow:時(shí),系統(tǒng)還會(huì)以某種方式在中間插入- (void)willChangeValueForKey:(NSString *)key、- (void)didChangeValueForKey:(NSString *)key和- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;的調(diào)用。有時(shí)候也會(huì)看到有人這么寫代碼:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"];
[super setValue:aDate forKey:@"now"];
[self didChangeValueForKey:@"now"];
}
這種寫法完全沒(méi)必要,也不要這樣做。如果這樣做的話,KVO代碼會(huì)被調(diào)用兩次。
KVO在調(diào)用存取方法之前總是調(diào)用- (void)willChangeValueForKey:(NSString *)key,之后總是調(diào)用- (void)didChangeValueForKey:(NSString *)key就是通過(guò)isa混寫(isa-swizzling)。第一次對(duì)一個(gè)對(duì)象調(diào)用- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;時(shí),框架會(huì)創(chuàng)建這個(gè)類的新的KVO子類,并被觀察對(duì)象轉(zhuǎn)換為新子類的對(duì)象在這個(gè)KVO的特殊子類中,Cocoa創(chuàng)建觀察屬性的setter,大致工作原理如下:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"];
[super setValue:aDate forKey:@"now"];
[self didChangeValueForKey:@"now"];
}
這種繼承和方法注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。這就是正確命名重要的原因。只有在使用KVC命名約定時(shí),KVO才能做到這一點(diǎn)。
KVO在實(shí)現(xiàn)中通過(guò)isa混寫(isa-swizzling)把這個(gè)對(duì)象的isa指針指向新創(chuàng)建的子類。Apple還重寫了-class方法并返回原來(lái)的類,企圖欺騙我們:這個(gè)類沒(méi)有變,就是原本的那個(gè)類。假設(shè)“被監(jiān)聽的對(duì)象”的類對(duì)象是MyClass,有時(shí)候我們能看到對(duì)NSKVONotifying_MyClass的引用而不是對(duì)MyClass的引用。借此我們得以知道Apple使用了isa混寫(isa-swizzling)
那么- (void)willChangeValueForKey:(NSString *)key、- (void)didChangeValueForKey:(NSString *)key和- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;這三個(gè)方法的執(zhí)行順序是怎樣的?
回調(diào)的時(shí)機(jī)就是在調(diào)用- (void)didChangeValueForKey:(NSString *)key時(shí)。
整個(gè)調(diào)用順序是:- (void)willChangeValueForKey:(NSString *)key后,在- (void)didChangeValueForKey:(NSString *)key內(nèi)部調(diào)用- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
54. IBOutlet連出來(lái)的視圖屬性為什么可以被設(shè)置成weak?
參考鏈接:Should IBOutlets be strong or weak under ARC?
文章告訴我們:
既然有外鏈那么視圖和xib或者storyboard中肯定存在,視圖已經(jīng)對(duì)踏有一個(gè)強(qiáng)引用了,所以可以用weak。
55. IBOutlets中User Defined Runtime Attributes如何使用?
它能夠通過(guò)KVC的方式配置一些在interface builder中不能配置的屬性。當(dāng)開發(fā)者希望在IBOutlets中做盡可能多的事情,這個(gè)特性能夠幫助開發(fā)者寫更加輕量級(jí)的viewController。
56. 如何調(diào)試BAD_ACCESS錯(cuò)誤?
四種方式:
- 重寫NSObject的respondsToSelector方法,打印出現(xiàn)EXEC_BAD_ACCESS前訪問(wèn)的最后一個(gè)Object對(duì)象;
-
通過(guò)Zombie;
Zombie - 設(shè)置全局?jǐn)帱c(diǎn)快速定位問(wèn)題代碼所在行;
- Xcode7 已經(jīng)集成了BAD_ACCESS捕獲功能:
使用clang test.c -o test -fsanitize=address命令編譯程序
運(yùn)行程序,得到如下結(jié)果:
image.png
57. lldb(gdb)常用的調(diào)試命令?
- breakpoint設(shè)置斷點(diǎn)定位到某一個(gè)函數(shù)
- N斷點(diǎn)指針下一步
- po打印對(duì)象
更多l(xiāng)ldb(gdb)調(diào)試命令可查看:

