《招一個靠譜的iOS》36-40

本人參考GitHub《招聘一個靠譜的iOS》面試題參考答案(下)
36. 不手動指定autoreleasepool的前提下,一個autorealese對象在什么時刻釋放?(比如在一個vc的viewDidLoad中創(chuàng)建)
37. BAD_ACCESS在什么情況下出現(xiàn)?
38. 蘋果是如何實現(xiàn)autoreleasepool的?
39. 使用block時什么情況會發(fā)生引用循環(huán),如何解決?
40. 在block內(nèi)如何修改block外部變量?

36. 不手動制定autoreleasepool的前提下,一個autorelease對象在什么時刻釋放?(比如在一個vc的viewDidLoad中創(chuàng)建)

(1)autorelease的釋放時機分為兩種情況:

  1. 手動干預(yù)釋放時機——指定autoreleasepool,就是所謂的當(dāng)前作用域大括號結(jié)束時釋放;
  2. 系統(tǒng)自動去釋放——不手動指定autoreleasepool;
    autorelease對象出了作用域之后,就會被添加到最近一次創(chuàng)建的自動釋放池中,并會在當(dāng)前的runloop迭代結(jié)束時釋放。
    釋放時機總結(jié)起來,可以用下圖表示:

    下面對這張圖進(jìn)行詳細(xì)的解釋:
    從程序啟動到加載完成是一個完整的運行循環(huán),然后會停下來,等待用戶交互,用戶的每一次交互都會啟動一次運行循環(huán)來處理用戶所有的點擊事件、觸摸事件。
    所有的autorelease對象,在出了作用域之后,會被自動添加到最近創(chuàng)建的自動釋放池中。但是如果每次都放進(jìn)應(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í)行前就被銷毀了。

37.BAD_ACCESS在什么情況下出現(xiàn)?

訪問了懸垂指針(野指針),比如:對一個已經(jīng)釋放了的對象執(zhí)行release、訪問已經(jīng)釋放對象的成員變量或者發(fā)消息、死循環(huán)等會出現(xiàn)BAD_ACCESS

38. 蘋果是如何實現(xiàn)autoreleasepool的?

autoreleasepool以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成:

  1. objc_autoreleasepoolPush
  2. objc_autoreleasepoolPop
  3. objc_autorelease

看函數(shù)名就可以知道,對autorelease分別執(zhí)行push、pop操作,銷毀對象時執(zhí)行release操作。
舉例說明:用類方法創(chuàng)建的對象都是autorelease的,那么一旦Person出了作用域,當(dāng)在Person的dealloc方法中打上斷點,就可以看到這樣的堆棧信息:


39.使用block時什么情況下會發(fā)生引用循環(huán),如何解決?

一個對象中強引用了block,在block中又強引用了該對象,就會發(fā)生循環(huán)引用。
解決方法是將該對象使用__weak或者_(dá)_block修飾符修飾之后再在block中使用。

  1. id weak weakSelf = self;或者weak __typeof(&*self)weakSelf = self該方法可以設(shè)置宏
  2. id __block weakSelf = self;
    或者將其中一方強制置空 xxx = nil;
    檢測代碼中是否存在循環(huán)引用問題,可以使用Facebook開源的一個監(jiān)測工具:FBRetainCycleDetector

40. 在block內(nèi)如何修改block外部變量?

默認(rèn)情況下,在block中訪問外部變量都是復(fù)制值過去,即:寫操作不對原變量生效,開發(fā)者可以加上__block來讓其寫操作生效。
為什么加上__block寫操作就生效了?
原因是:block不允許修改外部變量的值,這里所說的外部變量的值,指的是棧中指針的內(nèi)存地址。__block所起的作用就是只要觀察到該變量被block所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中,進(jìn)而在block內(nèi)部可以修改外部變量的值。
Apple設(shè)計block不允許修改外部變量的值,是考慮了block的特殊性。block也屬于“函數(shù)”的范疇,變量進(jìn)入block,實際就是改變了作用域。如果不加上這樣的限制,在幾個作用域之間進(jìn)行切換時,變量的可維護(hù)性將大大降低,比如:在block內(nèi)聲明了一個與block外部同名的變量,此時是允許還是不允許呢?只有加上了這樣的限制,這樣的場景才可以實現(xiàn),棧區(qū)變成了紅燈區(qū),堆區(qū)變成了綠燈區(qū)。

   __block int a = 0;
   NSLog(@"定義前:%p", &a);         //棧區(qū)
   void (^foo)(void) = ^{
       a = 1;
       NSLog(@"block內(nèi)部:%p", &a);    //堆區(qū)
   };
   NSLog(@"定義后:%p", &a);         //堆區(qū)
   foo();
2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定義前:0x16fda86f8
2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定義后:0x155b22fc8
2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] block內(nèi)部: 0x155b22fc8

“定義后”和“block內(nèi)部”兩者的內(nèi)存地址是一樣的,我們都知道 block 內(nèi)部的變量會被 copy 到堆區(qū),“block內(nèi)部”打印的是堆地址,因而也就可以知道,“定義后”打印的也是堆的地址。
如何證明“block內(nèi)部”打印的是堆地址?
把三個16進(jìn)制的內(nèi)存地址轉(zhuǎn)成10進(jìn)制就是:
定義后前:6171559672
block內(nèi)部:5732708296
定義后后:5732708296
中間相差438851376個字節(jié),也就是418.5M的空間,因為堆的地址要小于棧地址,又因為iOS中一個進(jìn)程的棧區(qū)內(nèi)存只有1M,Mac也只有8M,顯然a已經(jīng)在堆區(qū)了。
這也證實了:a在定義前是棧區(qū),但是只要進(jìn)入了block區(qū)域,就變成了堆區(qū),這才是__block關(guān)鍵字的真正作用。

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

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

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