1. 程式人生 > Android開發 >iOS block原理詳解

iOS block原理詳解

  1. block本質
  • block底層就是一個struct __main_block_impl_0型別的結構體,這個結構體中包含一個isa指標,本質上是一個OC物件
  • block是封裝了函式呼叫以及函式呼叫環境OC物件
  1. block底層結構 block底層結構就是__main_block_impl_0結構體,內部包含了impl結構體Desc結構體以及外部需要訪問的變數block將需要執行的程式碼放到一個函式裡,impl內部的FuncPtr指向這個函式的地址,通過地址呼叫這個函式,就可以執行block裡面的程式碼了。Desc用來描述block,內部的reserved作保留,Block_size

    描述block佔用記憶體

  2. block的變數捕獲 區域性變數block訪問方式是值傳遞auto自動變數可能會銷燬,記憶體可能會消失,不採用指標訪問; 區域性靜態變數block訪問方式是指標傳遞static變數一直儲存在記憶體中,指標訪問即可; 全域性變數、靜態全域性變數block不需要對變數捕獲,直接取值

// block的變數捕獲程式碼解析如下
auto int age = 10;
static int height = 10;    
void (^block)(void) = ^{
    NSLog(@"age is %d,height is %d",age,height);
};        
age = 20;
height = 20;        
block();
-------------------------------------------------
output: age is 10,height is 20

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age; // 值傳遞
  int *height; // 指標傳遞
}
複製程式碼
  1. block的型別
block型別 環境 儲存域 copy操作後
__NSGlobalBlock__ 沒有訪問auto變數 資料區 什麼也不做,型別不改變
__NSStackBlock__ 訪問了auto變數 棧區 從棧複製到堆,型別改變為__NSMallocBlock__
__NSMallocBlock__ __NSStackBlock__呼叫了copy 堆區 引用計數+1,型別不改變
  • ARC環境下,編譯器會根據以下幾種情況自動將棧上的block複製到堆上: 1、block作為函式返回值時,比如使用= 2、將block賦值給__strong指標時 3、block
    作為Cocoa API中方法名含有usingBlock的方法引數時 4、block作為GCD API的方法引數時
  1. 物件型別的auto變數
  • block內部訪問了物件型別的auto變數時: 如果block在棧空間,不論是ARC還是MRC環境,不管外部變數強引用還是弱引用block都會弱引用訪問物件 如果block在堆空間,如果外部強引用block內部也是強引用;如果外部弱引用block內部也是弱引用
  • 棧block: a) 如果block是在棧上,將不會對auto變數產生強引用 b) 棧上的block隨時會被銷燬,也沒必要去強引用其他物件
  • 堆block: 1、如果block被拷貝到堆上 a) 會呼叫block內部的copy函式 b) copy函式內部會呼叫_Block_object_assign函式 c) _Block_object_assign函式會根據auto變數的修飾符__strong__weak__unsafe_unretained做出相應的操作,形成強引用或者弱引用 2、如果block從堆上移除 a) 會呼叫block內部的dispose函式 b) dispose函式內部會呼叫_Block_object_dispose函式 c) _Block_object_dispose函式會自動釋放引用的auto變數(release,引用計數-1,若為0,則銷燬)
  1. __block
  • __block修飾符作用: __block可以用於解決block內部無法修改auto變數值的問題 __block不能修飾全域性變數、靜態變數static
  • __block修飾符原理: 編譯器會將__block變數包裝成一個結構體__Block_byref_age_0,結構體內部*__forwarding是指向自身的指標,內部還儲存著外部auto變數的值 __blockforwarding指標如下圖:

棧上__block結構體中的__forwarding指標指向自己,一旦複製到堆上棧上的__block結構體中的__forwarding指標會指向堆上的__block結構體,堆上__block結構體中的__forwarding還是指向自己。假設age棧上的變數,age->__forwarding會拿到堆上的__block結構體,age->__forwarding->age會把20賦值到堆上,不論是棧上還是堆上的__block結構體,都能保證20賦值到堆的結構體

  1. 思考題:block修改NSMutableString、NSMutableArray、NSMutableDictionary,需不需要新增__block 題目如下:以下程式碼是否可以正確執行
int main(int argc,const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [NSMutableArray array];
        void (^block)(void) = ^{
            [array addObject: @"5"];
            [array addObject: @"5"];
            NSLog(@"%@",array);
        };
        block();
    }
    return 0;
}
複製程式碼

分析:可以正確執行,因為在block塊中僅僅是使用了array的記憶體地址,往記憶體地址新增內容,並沒有修改arry的記憶體地址,因此array不需要使用__block修飾也可以正確編譯。當僅僅是使用區域性變數的記憶體地址,而不是修改的時候,儘量不要新增__block,通過上述分析我們知道一旦添加了__block修飾符,系統會自動建立相應的結構體,佔用不必要的記憶體空間

附:我的部落格地址