iOS基礎(chǔ)深入補(bǔ)完計劃--Block相關(guān)原理探究

前文地址:《iOS基礎(chǔ)深入補(bǔ)完計劃》

在前文、我們提到了property中的關(guān)鍵字copy可以修飾block。
那么、block究竟有哪些特殊。
block該怎樣聲明
__block、__weak、__strong是如何工作的呢。
__block又是否可以解決循環(huán)引用呢?

首先、關(guān)于block的作用域

  • 總的來說。block默認(rèn)是存檔在棧中(作用于被限制在當(dāng)前上下文(函數(shù), 方法...))、可能被隨時回收(比如代碼塊執(zhí)行完畢、會將塊內(nèi)變量內(nèi)存回收)。通過copy操作可以使其在堆中保留一份,、相當(dāng)于一直強(qiáng)引用著。以此保證在執(zhí)行block對象時、程序不會因為執(zhí)行一個空對象而崩潰。
舉個例子
   @interface ViewController ()
   
   @property (nonatomic, copy) void(^myblock)();
   
   @end
   
   @implementation ViewController
   
   - (void)viewDidLoad {
       [super viewDidLoad];
       // Do any additional setup after loading the view, typically from a nib.
   
       [self test];//當(dāng)此代碼結(jié)束時,test函數(shù)中的所有存儲在棧區(qū)的變量都會被系統(tǒng)釋放。
       self.myblock();
   
   }
   
   - (void)test {
       int n = 5;
       [self setMyblock:^{
           NSLog(@"%d",n);
       }];
   }
之后、把copy聲明換成assign。

EXC_BAD_ACCESS、使用了一個野指針。


屏幕快照 2017-12-04 下午5.53.09.png
此外、我們還可以進(jìn)一步確認(rèn)崩潰的原因。
屏幕快照 2017-12-05 上午10.12.01.png
之后、到底是不是因為局部變量被釋放、導(dǎo)致的崩潰?
  • 用成員變量試試。


    WechatIMG210.jpeg

    看來無論是否使用成員變量、內(nèi)部的變量指針都會被釋放


    WechatIMG211.jpeg
結(jié)論:
  • copy聲明會把變量在block保存一份指針變量、指向原對象地址。保證block內(nèi)部所引用變量不會被自動釋放而造成崩潰。
  • assign聲明的block、內(nèi)部引用的變量(無論是否是局部變量)在出了賦值當(dāng)前作用域之后、會被釋放。導(dǎo)致崩潰。
除此之外:
  • block聲明為成員變量、用strong修飾。也是沒有問題的、作用和copy一樣。
  • block聲明為成員變量、不加任何修飾。效果也和copy/strong一樣。
  • block聲明為局部變量、不加任何修飾。效果也和copy/strong一樣。

關(guān)于copy之后block的一些特性

會對其內(nèi)部的使用對象引用計數(shù)+1

這也是為什么需要用__weak去修飾self以防止循環(huán)引用的原因。
更多的解釋可以參閱《__weak與__block區(qū)別,深層理解兩者區(qū)別》《深入分析 Objective-C block、weakself、strongself 實現(xiàn)原理》

原理總結(jié)下來就是

__weak會新開辟一個變量指針、指向被修飾的變量地址、但不會對該變量增加引用
當(dāng)被修飾變量被釋放、__weak變量指針依舊會指向原有變量地址、而該地址則會返回空。
所以需要用__strong在block內(nèi)部修飾、讓block內(nèi)的變量不會在中途被釋放掉、保證block內(nèi)容一致性。

WechatIMG204.jpeg


默認(rèn)情況下、內(nèi)外部的變量數(shù)據(jù)不會互通。

   int a = 1;
   
   int (^backValue)(int) = ^(int num) {
       return num+a;
   };
   
   a += 1;//這次+=1不會被傳遞到block中a上。
   int b = backValue(1);
   NSLog(@"b-->%d", b);
  //輸出結(jié)果b-->2
  • 當(dāng)然、我們可以__block來修飾這個a、使其在block內(nèi)部的指針也可以被修改


    屏幕快照 2017-12-04 下午6.11.00.png
  • 為什么用__block修飾就可以修改外部的變量、或者將外部變量的修改傳遞進(jìn)block內(nèi)了?

先寫結(jié)論、不愿意細(xì)看可以跳過。

  • 無論是普通變量、還是OC變量。在不使用__block的情況下、無法對其本身進(jìn)行修改。

因為二者、都只會在block執(zhí)行代碼中copy一份值/指向該對象地址的指針。即使修改了這個值/指針指向、也無法對外部造成影響。所以編譯器干脆就禁止這個操作了(雖然你可以通過直接操作指針的方式修改、但是被修改的依舊只是block內(nèi)部的指針變量而已)

  • OC對象在不使用__block修飾的情況下、可以修改其屬性。

自身的指針會被捕獲到block代碼塊中、借此操作該指針指向地址所屬對象的屬性。(這也是為什么在block里可以修改self.xxx屬性的原因)。

  • OC對象以及普通變量在使用__block修飾的情況下、可以對自身進(jìn)行修改。

會將原變量的指針從棧移動至堆區(qū)。由block對象進(jìn)行捕獲保存、并借此進(jìn)行修改。


以下是相關(guān)分析

這里有一篇文章、借助編譯后的block闡釋了__block的工作原理
《你真的理解__block修飾符的原理么》

  • 簡而言之、block捕獲普通變量其實將變量的值捕獲到了block執(zhí)行塊中。這個已經(jīng)與原變量無關(guān)。(由于無法直接獲得原變量、技術(shù)上無法實現(xiàn)修改、所以編譯器直接禁止了)。
    屏幕快照 2017-12-04 下午6.36.49.png

    block內(nèi)部的指針指向、與外部完全分離
  • __block聲明的變量、在被block捕獲時。會被block以指針變量的形式保存在自身處于堆中結(jié)構(gòu)體內(nèi)。同時、原對象的指針地址也被修改到了堆中。既然變量指針內(nèi)存地址相同、就代表是同一個指針、自然可以同步修改。

__block之后的效果

具體可見:《iOS中__block 關(guān)鍵字的底層實現(xiàn)原理》《一篇文章看懂iOS代碼塊Block》

  • 當(dāng)然、還有個其他情況。就是OC對象的屬性。

當(dāng)block執(zhí)行代碼需要捕獲的是OC對象時、block會自動在block代碼塊中copy一個對象指針(普通對象是copy值)、指向該對象當(dāng)前地址。
就會出現(xiàn)如下情況


屏幕快照 2017-12-04 下午6.57.07.png
不過、并不允許你為這個存在棧中的變量指針直接重新指定一個新的指向。除非使用__block將這個對象的地址直接移動到堆中。

此外、還做了另外一個實驗。


屏幕快照 2017-12-04 下午8.18.27.png
這也證明、即便是OC對象也僅僅是捕獲了當(dāng)前指針的指向。如果新生成了一個對象修改了外部的對象的指針指向地址、里面繼續(xù)修改也沒用了。

關(guān)于block的循環(huán)引用

  • __block是否可以防止循環(huán)引用。

可以、只需要在block內(nèi)部根據(jù)需要將變量置空。缺點是需要手動調(diào)用一次block

 __block id tmp = self;
 self.block = ^{
     tmp = nil;
 };
 self.block();
需要注意的是。在tmp被block的捕獲的時候、和__weak一樣是不會增加tmp的引用計數(shù)的。之所以要手動釋放、是因為在__block id tmp = self;時、tmp對self進(jìn)行了一次強(qiáng)引用。需要消除的是這次引用。而且、當(dāng)tmp被置nil之后、下次block調(diào)用將會得到一個空地址。但原對象不受此影響。
WechatIMG207.jpeg

最后

本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏、萬望留言斧正。如果不吝賜教小弟更加感謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容