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佔用內存 git

  2. block的變量捕獲 局部變量block訪問方式是值傳遞auto自動變量可能會銷燬,內存可能會消失,不採用指針訪問; 局部靜態變量block訪問方式是指針傳遞static變量一直保存在內存中,指針訪問便可; 全局變量、靜態全局變量block不須要對變量捕獲,直接取值 github

// 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複製到堆上: 一、block做爲函數返回值時,好比使用= 二、將block賦值給__strong指針時 三、block做爲Cocoa API中方法名含有usingBlock的方法參數時 四、block做爲GCD API的方法參數時
  1. 對象類型的auto變量
  • block內部訪問了對象類型的auto變量時: 若是block在棧空間,不管是ARC仍是MRC環境,無論外部變量強引用仍是弱引用block都會弱引用訪問對象 若是block在堆空間,若是外部強引用block內部也是強引用;若是外部弱引用block內部也是弱引用
  • 棧block: a) 若是block是在棧上,將不會對auto變量產生強引用 b) 棧上的block隨時會被銷燬,也不必去強引用其餘對象
  • 堆block: 一、若是block被拷貝到堆上 a) 會調用block內部的copy函數 b) copy函數內部會調用_Block_object_assign函數 c) _Block_object_assign函數會根據auto變量的修飾符__strong__weak__unsafe_unretained作出相應的操做,造成強引用或者弱引用 二、若是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賦值到堆的結構體bash

  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修飾符,系統會自動建立相應的結構體,佔用沒必要要的內存空間函數

附:個人博客地址ui

相關文章
相關標籤/搜索