- 猜想runloop內(nèi)部是如何實現(xiàn)的?
一般來講,一個線程一次只能執(zhí)行一個任務(wù),執(zhí)行完成后線程就會退出。如果我們需要一個機制,讓線程能隨時處理事件但并不退出,通常的代碼邏輯 是這樣的:
function loop() { initialize(); do { var message = get_next_message(); process_message(message); } while (message != quit);}
或使用偽代碼來展示下:
// // http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)// https://github.com/ChenYilongint main(int argc, char * argv[]) { //程序一直運行狀態(tài) while (AppIsRunning) { //睡眠狀態(tài),等待喚醒事件 id whoWakesMe = SleepForWakingUp(); //得到喚醒事件 id event = GetEvent(whoWakesMe); //開始處理事件 HandleEvent(event); } return 0;}
參考鏈接:
《深入理解RunLoop》
摘自博文CFRunLoop,原作者是微博@我就叫Sunny怎么了
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#32-objc使用什么機制管理對象內(nèi)存)32. objc使用什么機制管理對象內(nèi)存?
通過 retainCount 的機制來決定對象是否需要釋放。 每次 runloop 的時候,都會檢查對象的 retainCount,如果retainCount 為 0,說明該對象沒有地方需要繼續(xù)使用了,可以釋放掉了。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#33-arc通過什么方式幫助開發(fā)者管理內(nèi)存)33. ARC通過什么方式幫助開發(fā)者管理內(nèi)存?
編譯時根據(jù)代碼上下文,插入 retain/release
ARC相對于MRC,不是在編譯時添加retain/release/autorelease這么簡單。應(yīng)該是編譯期和運行期兩部分共同幫助開發(fā)者管理內(nèi)存。
在編譯期,ARC用的是更底層的C接口實現(xiàn)的retain/release/autorelease,這樣做性能更好,也是為什么不能在ARC環(huán)境下手動retain/release/autorelease,同時對同一上下文的同一對象的成對retain/release操作進行優(yōu)化(即忽略掉不必要的操作);ARC也包含運行期組件,這個地方做的優(yōu)化比較復(fù)雜,但也不能被忽略?!綯ODO:后續(xù)更新會詳細(xì)描述下】
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#34-不手動指定autoreleasepool的前提下一個autorealese對象在什么時刻釋放比如在一個vc的viewdidload中創(chuàng)建)34. 不手動指定autoreleasepool的前提下,一個autorealese對象在什么時刻釋放?(比如在一個vc的viewDidLoad中創(chuàng)建)
分兩種情況:手動干預(yù)釋放時機、系統(tǒng)自動去釋放。
手動干預(yù)釋放時機--指定autoreleasepool 就是所謂的:當(dāng)前作用域大括號結(jié)束時釋放。
系統(tǒng)自動去釋放--不手動指定autoreleasepool
Autorelease對象出了作用域之后,會被添加到最近一次創(chuàng)建的自動釋放池中,并會在當(dāng)前的 runloop 迭代結(jié)束時釋放。
下面對這張圖進行詳細(xì)的解釋:
從程序啟動到加載完成是一個完整的運行循環(huán),然后會停下來,等待用戶交互,用戶的每一次交互都會啟動一次運行循環(huán),來處理用戶所有的點擊事件、觸摸事件。
我們都是知道: 所有 autorelease 的對象,在出了作用域之后,會被自動添加到最近創(chuàng)建的自動釋放池中。
但是如果每次都放進應(yīng)用程序的 main.m
中的 autoreleasepool 中,遲早有被撐滿的一刻。這個過程中必定有一個釋放的動作。何時?
在一次完整的運行循環(huán)結(jié)束之前,會被銷毀。
那什么時間會創(chuàng)建自動釋放池?運行循環(huán)檢測到事件并啟動后,就會創(chuàng)建自動釋放池。
子線程的 runloop 默認(rèn)是不工作,無法主動創(chuàng)建,必須手動創(chuàng)建。
自定義的 NSOperation 和 NSThread 需要手動創(chuàng)建自動釋放池。比如: 自定義的 NSOperation 類中的 main 方法里就必須添加自動釋放池。否則出了作用域后,自動釋放對象會因為沒有自動釋放池去處理它,而造成內(nèi)存泄露。
但對于 blockOperation 和 invocationOperation 這種默認(rèn)的Operation ,系統(tǒng)已經(jīng)幫我們封裝好了,不需要手動創(chuàng)建自動釋放池。
@autoreleasepool 當(dāng)自動釋放池被銷毀或者耗盡時,會向自動釋放池中的所有對象發(fā)送 release 消息,釋放自動釋放池中的所有對象。
如果在一個vc的viewDidLoad中創(chuàng)建一個 Autorelease對象,那么該對象會在 viewDidAppear 方法執(zhí)行前就被銷毀了。
參考鏈接:《黑幕背后的Autorelease》
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#35-bad_access在什么情況下出現(xiàn))35. BAD_ACCESS在什么情況下出現(xiàn)?
訪問了野指針,比如對一個已經(jīng)釋放的對象執(zhí)行了release、訪問已經(jīng)釋放對象的成員變量或者發(fā)消息。 死循環(huán)
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#36-蘋果是如何實現(xiàn)autoreleasepool的)36. 蘋果是如何實現(xiàn)autoreleasepool的?
autoreleasepool 以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_autorelease
看函數(shù)名就可以知道,對 autorelease 分別執(zhí)行 push,和 pop 操作。銷毀對象時執(zhí)行release操作。
舉例說明:我們都知道用類方法創(chuàng)建的對象都是 Autorelease 的,那么一旦 Person 出了作用域,當(dāng)在 Person 的 dealloc 方法中打上斷點,我們就可以看到這樣的調(diào)用堆棧信息:
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#37-使用block時什么情況會發(fā)生引用循環(huán)如何解決)37. 使用block時什么情況會發(fā)生引用循環(huán),如何解決?
一個對象中強引用了block,在block中又使用了該對象,就會發(fā)射循環(huán)引用。 解決方法是將該對象使用__weak或者__block修飾符修飾之后再在block中使用。
id weak weakSelf = self; 或者 weak __typeof(&*self)weakSelf = self該方法可以設(shè)置宏
id __block weakSelf = self;
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#38-在block內(nèi)如何修改block外部變量)38. 在block內(nèi)如何修改block外部變量?
默認(rèn)情況下,在block中訪問的外部變量是復(fù)制過去的,即:寫操作不對原變量生效。但是你可以加上__block
來讓其寫操作生效,示例代碼如下:
__block int a = 0;void (^foo)(void) = ^{ a = 1; }f00(); //這里,a的值被修改為1
參考鏈接:微博@唐巧_boy的著作《iOS開發(fā)進階》中的第11.2.3章節(jié)
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#39-使用系統(tǒng)的某些block-api如uiview的block版本寫動畫時是否也考慮引用循環(huán)問題)39. 使用系統(tǒng)的某些block api(如UIView的block版本寫動畫時),是否也考慮引用循環(huán)問題?
系統(tǒng)的某些block api中,UIView的block版本寫動畫時不需要考慮,但也有一些api 需要考慮:
所謂“引用循環(huán)”是指雙向的強引用,所以那些“單向的強引用”(block 強引用 self )沒有問題,比如這些:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * notification) { self.someProperty = xyz; }];
這些情況不需要考慮“引用循環(huán)”。
但如果你使用一些參數(shù)中可能含有 ivar 的系統(tǒng) api ,如 GCD 、NSNotificationCenter就要小心一點:比如GCD 內(nèi)部如果引用了 self,而且 GCD 的其他參數(shù)是 ivar,則要考慮到循環(huán)引用:
__weak typeof(self) weakSelf = self;dispatch_group_async(_operationsGroup, _operationsQueue, ^{typeof(self) strongSelf = weakSelf;[strongSelf doSomething];[strongSelf doSomethingElse];} );
類似的:
__weak typeof(self) weakSelf = self; _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) { typeof(self) strongSelf = weakSelf; [strongSelf dismissModalViewControllerAnimated:YES]; }];
self --> _observer --> block --> self 顯然這也是一個循環(huán)引用。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#40-gcd的隊列dispatch_queue_t分哪兩種類型)40. GCD的隊列(dispatch_queue_t
)分哪兩種類型?
串行隊列Serial Dispatch Queue
并行隊列Concurrent Dispatch Queue
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#41-如何用gcd同步若干個異步調(diào)用如根據(jù)若干個url異步加載多張圖片然后在都下載完成后合成一張整圖)41. 如何用GCD同步若干個異步調(diào)用?(如根據(jù)若干個url異步加載多張圖片,然后在都下載完成后合成一張整圖)
使用Dispatch Group追加block到Global Group Queue,這些block如果全部執(zhí)行完畢,就會執(zhí)行Main Dispatch Queue中的結(jié)束處理的block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, queue, ^{ /*加載圖片1 / });dispatch_group_async(group, queue, ^{ /加載圖片2 / });dispatch_group_async(group, queue, ^{ /加載圖片3 */ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 合并圖片});
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#42-dispatch_barrier_async的作用是什么)42. dispatch_barrier_async
的作用是什么?
在并行隊列中,為了保持某些任務(wù)的順序,需要等待一些任務(wù)完成后才能繼續(xù)進行,使用 barrier 來等待之前任務(wù)完成,避免數(shù)據(jù)競爭等問題。 dispatch_barrier_async
函數(shù)會等待追加到Concurrent Dispatch Queue并行隊列中的操作全部執(zhí)行完之后,然后再執(zhí)行 dispatch_barrier_async
函數(shù)追加的處理,等 dispatch_barrier_async
追加的處理執(zhí)行結(jié)束之后,Concurrent Dispatch Queue才恢復(fù)之前的動作繼續(xù)執(zhí)行。
打個比方:比如你們公司周末跟團旅游,高速休息站上,司機說:大家都去上廁所,速戰(zhàn)速決,上完廁所就上高速。超大的公共廁所,大家同時去,程序猿很快就結(jié)束了,但程序媛就可能會慢一些,即使你第一個回來,司機也不會出發(fā),司機要等待所有人都回來后,才能出發(fā)。 dispatch_barrier_async
函數(shù)追加的內(nèi)容就如同 “上完廁所就上高速”這個動作。
(注意:使用 dispatch_barrier_async
,該函數(shù)只能搭配自定義并行隊列 dispatch_queue_t
使用。不能使用:dispatch_get_global_queue
,否則 dispatch_barrier_async
的作用會和 dispatch_async
的作用一模一樣。 )
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#43-蘋果為什么要廢棄dispatch_get_current_queue)43. 蘋果為什么要廢棄dispatch_get_current_queue
?
dispatch_get_current_queue
容易造成死鎖
[
- (void)viewDidLoad{ [super viewDidLoad]; NSLog(@"1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3");}
只輸出:1 。發(fā)生主線程鎖死。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#45-addobserverforkeypathoptionscontext各個參數(shù)的作用分別是什么observer中需要實現(xiàn)哪個方法才能獲得kvo回調(diào))45. addObserver:forKeyPath:options:context:各個參數(shù)的作用分別是什么,observer中需要實現(xiàn)哪個方法才能獲得KVO回調(diào)?
// 添加鍵值觀察/1 觀察者,負(fù)責(zé)處理監(jiān)聽事件的對象2 觀察的屬性3 觀察的選項4 上下文/[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
observer中需要實現(xiàn)一下方法:
// 所有的 kvo 監(jiān)聽到事件,都會調(diào)用此方法/* 1. 觀察的屬性 2. 觀察的對象 3. change 屬性變化字典(新/舊) 4. 上下文,與監(jiān)聽的時候傳遞的一致 */- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#46-如何手動觸發(fā)一個value的kvo)46. 如何手動觸發(fā)一個value的KVO
所謂的“手動觸發(fā)”是區(qū)別于“自動觸發(fā)”:
自動觸發(fā)是指類似這種場景:在注冊 KVO 之前設(shè)置一個初始值,注冊之后,設(shè)置一個不一樣的值,就可以觸發(fā)了。
想知道如何手動觸發(fā),必須知道自動觸發(fā) KVO 的原理:
鍵值觀察通知依賴于 NSObject 的兩個方法: willChangeValueForKey:
和 didChangevlueForKey:
。在一個被觀察屬性發(fā)生改變之前, willChangeValueForKey:
一定會被調(diào)用,這就 會記錄舊的值。而當(dāng)改變發(fā)生后, didChangeValueForKey:
會被調(diào)用,繼而 observeValueForKey:ofObject:change:context:
也會被調(diào)用。如果可以手動實現(xiàn)這些調(diào)用,就可以實現(xiàn)“手動觸發(fā)”了。
那么“手動觸發(fā)”的使用場景是什么?一般我們只在希望能控制“回調(diào)的調(diào)用時機”時才會這么做。
具體做法如下:
如果這個 value
是 表示時間的 self.now
,那么代碼如下:最后兩行代碼缺一不可。
// .m文件// Created by https://github.com/ChenYilong// 微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/).// 手動觸發(fā) value 的KVO,最后兩行代碼缺一不可。//@property (nonatomic, strong) NSDate *now;- (void)viewDidLoad{ [super viewDidLoad]; [self willChangeValueForKey:@"now"]; // “手動觸發(fā)self.now的KVO”,必寫。 [self didChangeValueForKey:@"now"]; // “手動觸發(fā)self.now的KVO”,必寫。}
但是平時我們一般不會這么干,我們都是等系統(tǒng)去“自動觸發(fā)”?!白詣佑|發(fā)”的實現(xiàn)原理:
比如調(diào)用 setNow:
時,系統(tǒng)還會以某種方式在中間插入 wilChangeValueForKey:
、 didChangeValueForKey:
和observeValueForKeyPath:ofObject:change:context:
的調(diào)用。
大家可能以為這是因為 setNow:
是合成方法,有時候我們也能看到人們這么寫代碼:
- (void)setNow:(NSDate *)aDate { [self willChangeValueForKey:@"now"]; // 沒有必要 _now = aDate; [self didChangeValueForKey:@"now"];// 沒有必要}
這是完全沒有必要的代碼,不要這么做,這樣的話,KVO代碼會被調(diào)用兩次。KVO在調(diào)用存取方法之前總是調(diào)用willChangeValueForKey:
,之后總是調(diào)用 didChangeValueForkey:
。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)。下文《apple用什么方式實現(xiàn)對一個對象的KVO?》會有詳述。
參考鏈接: Manual Change Notification---Apple 官方文檔
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#47-若一個類有實例變量-nsstring-_foo-調(diào)用setvalueforkey時可以以foo還是-_foo-作為key)47. 若一個類有實例變量 NSString *_foo
,調(diào)用setValue:forKey:時,可以以foo還是 _foo
作為key?
都可以。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#48-kvc的keypath中的集合運算符如何使用)48. KVC的keyPath中的集合運算符如何使用?
必須用在集合對象上或普通對象的集合屬性上
簡單集合運算符有@avg, @count , @max , @min ,@sum,
格式 @"@sum.age"或 @"集合屬性.@max.age"
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#49-kvc和kvo的keypath一定是屬性么)49. KVC和KVO的keyPath一定是屬性么?
KVO支持實例變量
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#50-如何關(guān)閉默認(rèn)的kvo的默認(rèn)實現(xiàn)并進入自定義的kvo實現(xiàn))50. 如何關(guān)閉默認(rèn)的KVO的默認(rèn)實現(xiàn),并進入自定義的KVO實現(xiàn)?
請參考:《如何自己動手實現(xiàn) KVO》
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#51-apple用什么方式實現(xiàn)對一個對象的kvo)51. apple用什么方式實現(xiàn)對一個對象的KVO?
Apple 的文檔對 KVO 實現(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 of the observed object is modified, pointing to an intermediate class rather than at the true class ...
從Apple 的文檔可以看出:Apple 并不希望過多暴露 KVO 的實現(xiàn)細(xì)節(jié)。不過,要是借助 runtime 提供的方法去深入挖掘,所有被掩蓋的細(xì)節(jié)都會原形畢露:
當(dāng)你觀察一個對象時,一個新的類會被動態(tài)創(chuàng)建。這個類繼承自該對象的原本的類,并重寫了被觀察屬性的 setter 方法。重寫的 setter 方法會負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對象:值的更改。最后通過 isa 混寫(isa-swizzling)
把這個對象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個對象的類是什么 ) 指向這個新創(chuàng)建的子類,對象就神奇的變成了新創(chuàng)建的子類的實例。我畫了一張示意圖,如下所示:
KVO 確實有點黑魔法:
Apple 使用了 isa 混寫(isa-swizzling)
來實現(xiàn) KVO 。
下面做下詳細(xì)解釋:
鍵值觀察通知依賴于 NSObject 的兩個方法: willChangeValueForKey:
和 didChangevlueForKey:
。在一個被觀察屬性發(fā)生改變之前, willChangeValueForKey:
一定會被調(diào)用,這就會記錄舊的值。而當(dāng)改變發(fā)生后, didChangeValueForKey:
會被調(diào)用,繼而 observeValueForKey:ofObject:change:context:
也會被調(diào)用??梢允謩訉崿F(xiàn)這些調(diào)用,但很少有人這么做。一般我們只在希望能控制回調(diào)的調(diào)用時機時才會這么做。大部分情況下,改變通知會自動調(diào)用。
比如調(diào)用 setNow:
時,系統(tǒng)還會以某種方式在中間插入 wilChangeValueForKey:
、 didChangeValueForKey:
和observeValueForKeyPath:ofObject:change:context:
的調(diào)用。大家可能以為這是因為 setNow:
是合成方法,有時候我們也能看到人們這么寫代碼:
- (void)setNow:(NSDate *)aDate { [self willChangeValueForKey:@"now"]; // 沒有必要 _now = aDate; [self didChangeValueForKey:@"now"];// 沒有必要}
這是完全沒有必要的代碼,不要這么做,這樣的話,KVO代碼會被調(diào)用兩次。KVO在調(diào)用存取方法之前總是調(diào)用willChangeValueForKey:
,之后總是調(diào)用 didChangeValueForkey:
。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)。第一次對一個對象調(diào)用 addObserver:forKeyPath:options:context:
時,框架會創(chuàng)建這個類的新的 KVO 子類,并將被觀察對象轉(zhuǎn)換為新子類的對象。在這個 KVO 特殊子類中, Cocoa 創(chuàng)建觀察屬性的 setter ,大致工作原理如下:
- (void)setNow:(NSDate *)aDate { [self willChangeValueForKey:@"now"]; [super setValue:aDate forKey:@"now"]; [self didChangeValueForKey:@"now"];}
這種繼承和方法注入是在運行時而不是編譯時實現(xiàn)的。這就是正確命名如此重要的原因。只有在使用KVC命名約定時,KVO才能做到這一點。
KVO 在實現(xiàn)中通過 isa 混寫(isa-swizzling)
把這個對象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個對象的類是什么 ) 指向這個新創(chuàng)建的子類,對象就神奇的變成了新創(chuàng)建的子類的實例。這在Apple 的文檔可以得到印證:
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 在實現(xiàn)中使用了 isa 混寫( isa-swizzling)
,這個的確不是很容易發(fā)現(xiàn):Apple 還重寫、覆蓋了 -class
方法并返回原來的類。 企圖欺騙我們:這個類沒有變,就是原本那個類。。。
但是,假設(shè)“被監(jiān)聽的對象”的類對象是 MYClass
,有時候我們能看到對 NSKVONotifying_MYClass
的引用而不是對MYClass
的引用。借此我們得以知道 Apple 使用了 isa 混寫(isa-swizzling)
。具體探究過程可參考 這篇博文 。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#52-iboutlet連出來的視圖屬性為什么可以被設(shè)置成weak)52. IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?
參考鏈接: Should IBOutlets be strong or weak under ARC?
文章告訴我們:
因為既然有外鏈那么視圖在xib或者storyboard中肯定存在,視圖已經(jīng)對它有一個強引用了。
不過這個回答漏了個重要知識,使用storyboard(xib不行)創(chuàng)建的vc,會有一個叫_topLevelObjectsToKeepAliveFromStoryboard的私有數(shù)組強引用所有top level的對象,所以這時即便outlet聲明成weak也沒關(guān)系
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#53-ib中user-defined-runtime-attributes如何使用)53. IB中User Defined Runtime Attributes如何使用?
它能夠通過KVC的方式配置一些你在interface builder 中不能配置的屬性。當(dāng)你希望在IB中作盡可能多得事情,這個特性能夠幫助你編寫更加輕量級的viewcontroller
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#54-如何調(diào)試bad_access錯誤)54. 如何調(diào)試BAD_ACCESS錯誤
重寫object的respondsToSelector方法,現(xiàn)實出現(xiàn)EXEC_BAD_ACCESS前訪問的最后一個object
通過 Zombie
設(shè)置全局?jǐn)帱c快速定位問題代碼所在行
Xcode 7 已經(jīng)集成了BAD_ACCESS捕獲功能:Address Sanitizer。 用法如下:在配置中勾選?
Enable Address Sanitizer
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#55-lldbgdb常用的調(diào)試命令)55. lldb(gdb)常用的調(diào)試命令?
breakpoint 設(shè)置斷點定位到某一個函數(shù)
n 斷點指針下一步
po打印對象
更多 lldb(gdb) 調(diào)試命令可查看
The LLDB Debugger ;
蘋果官方文檔: iOS Debugging Magic 。
Posted by 微博@iOS程序犭袁原創(chuàng)文章,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0