《Objective-C高級編程》Blocks

《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

2.1 Blcoks概要

2.1.1 什么是Blocks

Blocks是C語言的擴充功能——“帶有自動變量(即局部變量)的匿名函數(shù)”。

使用Blocks可以不聲明C++和Objective-C類,也沒有使用靜態(tài)變量、靜態(tài)全局變量或全局變量時的問題,僅用編程C語言函數(shù)的源代碼量即可使用帶有自動變量值的匿名函數(shù)。

2.2 Blocks模式

2.2.1 Block語法

  • Block常量表達式:

圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

??:

^ int (int count){
        return count +1;
    };
  • 省略返回值類型的Block常量表達式:

圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

省略返回值類型時,如果表達式有return語句就返回該返回值的類型。如果表達式?jīng)]有return語句就返回void類型。如果表達式有多個return語句,所有的return語句返回值的類型必須相同。
??:

^ int (int count){
        return count +1;
    };

省略int類型返回值:

^ (int count){
        return count +1;
    };

??:

^ void (int count){
        count +1;
        NSLog(@"%@",count+1);
    };

省略void類型:

^ (int count){
        count +1;
        NSLog(@"%@",count+1);
    };
  • 省略返回值類型和參數(shù)列表的Block常量表達式:

圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

如果不使用參數(shù),參數(shù)列表也可省略。
??:

^ void (void){
        NSLog(@"Hello world");
    };

省略void類型:

^ {
        NSLog(@"Hello world");
    };

2.2.2 Block類型變量

在Block語法下,可將Block語法賦值給聲明為Block類型的變量中(即源代碼中一旦使用Block語法就相當于生成了可賦值給Block類型變量的“值”)。
??:

int (^blk) (int);
“Block”即指源代碼中的Block語法,也指由Block語法所生成的值。
  • 使用Block語法將Block賦值為Block類型變量。

??:

int (^blk) (int) = ^ int (int count){
        return count +1;
    };
  • 由Block類型變量向Block類型變量賦值。

??:

int (^blk1) (int) = blk;
int (^blk2) (int);
 blk2 = blk1;
  • Block類型變量作為函數(shù)參數(shù)傳遞。

??:

-(void)blockFunc:(int (^)(int))blk;
  • Block類型變量作為函數(shù)返回值返回。

??:

-(int)blockFunc1{
    int (^blk) (int)  = ^(int count){
        return count +1;
    };
    return blk(1);
}
//等同于:
-(int)blockFunc1{
    return ^(int count){
        return count +1;
    }(1);
}
  • Block類型變量作為函數(shù)參數(shù)和返回值時,可以通過typedef為Block類型提供別名,從而起到簡化塊類型變量名的作用。

??:

typedef int (^BLK) (int);//
-(void)blockFunc:(BLK)blk;//作為函數(shù)參數(shù)
-(int)blockFunc1{
    BLK blk = ^(int count){
        return count +1;
    };
    return blk(1);
}//作為返回值

2.2.3 截獲自動變量值

Blocks中,Block常量表達式會截獲所使用的自動變量的值(即保存該自動變量的瞬間值),從而在執(zhí)行塊時使用。
??:

{
    int val = 10;
    void (^blk)() = ^{
       NSLog(@"%d",val);
    };
    val = 2;
    blk();
}//輸出為10;不是2;

2.2.4 __block說明符()

使用附有 __block說明符的自動變量可在Block中賦值該變量稱為 __block 變量。__block說明符也被稱之為存儲類型修改符。
??:

{
   __block int val = 10;
    void (^blk)() = ^{
        var = 1;
       NSLog(@"%d",val);
    };
    val = 2;
    blk();
}//輸出為1;不是2或者10;

2.2.5 截獲的自動變量

  • 如果給Block中截獲的自動變量賦值,需要給截獲的自動變量附加__block說明符。
  • 截獲Objective-C對象,調(diào)用變更該對象的方法并不會產(chǎn)生編譯錯誤,但是,向截獲的自動變量(即所截獲的Objective-C對象)賦值則會產(chǎn)生錯誤。
  • 在使用C語言數(shù)組時,Block中的截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲,需要通過指針實現(xiàn)對C語言數(shù)組自動變量的截獲。

??:

const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
blk();

2.3 Blocks的實現(xiàn)

2.3.1 Block的實質(zhì)

Block實質(zhì)是Objective-C對閉包的對象實現(xiàn),簡單說來,Block就是對象。
將Objective-C的代碼轉(zhuǎn)化為C++的代碼來理解Block的實現(xiàn)。
Objective-C 轉(zhuǎn) C++的方法:
- 在OC源文件block.m寫好代碼。
- 打開終端,cd到block.m所在文件夾。
- 輸入clang -rewrite-objc block.m,就會在當前文件夾內(nèi)自動生成對應(yīng)的block.cpp文件。

Objective-C中Block的實現(xiàn):
int main()
{
    void (^blk)(void) = ^{
        printf("Block\n");
    };
    blk();
    return 0;
}
轉(zhuǎn)化為C++代碼:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;      // 標志
    int Reserved;   // 今后版本升級所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
};


// block結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc;

    // Block的構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
        // _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員。(將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};


// 將來被調(diào)用的block內(nèi)部的代碼:block值被轉(zhuǎn)換為C的函數(shù)代碼
// __ceself為指向Block值的變量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}

// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
    unsigned long reserved;     // 今后版本升級所需的區(qū)域
    unsigned long Block_size;   // Block的大小
} __mian_block_desc_0_DATA = { // 該結(jié)構(gòu)體實例的初始化部分
    0,
    sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實例)的大小進行初始化
};

// main函數(shù)
int main()
{
    // 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)__main_block_impl_0
    void (*blk)(void) =
        (void (*)(void)) & __main_block_impl_0(
            (void *)__main_block_func_0, &__mian_block_desc_0_DATA);

    //調(diào)用block
    ((void (*)(struct __block_impl *))(
        (struct __block_impl *)blk)->FuncPtr) ((struct __block_impl *)blk);
    return 0;
}
其中,Objective-C 中Block值轉(zhuǎn)化而來的C++代碼為:
// 將來被調(diào)用的block內(nèi)部的代碼:block值被轉(zhuǎn)換為C的函數(shù)代碼
// __ceself為指向Block值的變量。那么,*__cself 就是是指向Block的值的指針,也就相當于是Block的值它自己(OC里的self)。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("Block\n");
}
可以看出,Block結(jié)構(gòu)體就是__main_block_impl_0結(jié)構(gòu)體。Block的值就是通過__main_block_impl_0構(gòu)造出來的。Block結(jié)構(gòu)體的聲明:
// block結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc;

    // Block的構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
        // _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員。(將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
其中,__main_block_impl_0結(jié)構(gòu)體有三個部分:
  • 第一個是成員變量impl,它是實際的函數(shù)指針,它指向__main_block_func_0。impl結(jié)構(gòu)體的聲明:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;      // 標志
    int Reserved;   // 今后版本升級所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
};
  • 第二個是成員變量是指向__main_block_desc_0結(jié)構(gòu)體的Desc指針,是用于描述當前這個block的附加信息。
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
    unsigned long reserved;     // 今后版本升級所需的區(qū)域
    unsigned long Block_size;   // Block的大小
} __mian_block_desc_0_DATA = { // 該結(jié)構(gòu)體實例的初始化部分
    0,
    sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實例)的大小進行初始化
};
  • 第三個部分是__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù),__main_block_impl_0 就是該 block 的實現(xiàn)。
 // Block的構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
        // _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員。(將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
在這個結(jié)構(gòu)體的構(gòu)造函數(shù)里:
  • isa指針保持這所屬類的結(jié)構(gòu)體的實例的指針。
  • __main_block_imlp_0結(jié)構(gòu)體就相當于Objective-C類對象的結(jié)構(gòu)體
  • _NSConcreteStackBlock相當于Block的結(jié)構(gòu)體實例
也就是說Block實質(zhì)是Objective-C對閉包的對象實現(xiàn),簡單說來,Block就是對象。
Objective-C類與對象的實質(zhì)是什么呢?
Objective-C類與對象的實質(zhì):
  • Objective-C中的對象就是一個結(jié)構(gòu)體,并且所有的對象都有一個相同的結(jié)構(gòu)體(即Class是類,id是對象)。而且每一個對象都有一個isa指針,這個isa指向生成該對象的類。

  • Objective-C類與對象的實現(xiàn)中最基本的結(jié)構(gòu)體為objc_object結(jié)構(gòu)體和objc_class結(jié)構(gòu)體。其中:

  • id類型是objc_object結(jié)構(gòu)體的指針類型。

typedef struct objc_object {
       Class isa;
} *id;
  • Class是objc_class結(jié)構(gòu)體的指針類型。
typedef struct objc_class *Class;
struct objc_class {
       Class isa;
} ;

通過一個簡單的MyObject類來說明Objective-C類與對象的實質(zhì):
??:

@interface MyObject : NSObject
{
 int val0;
 int val1;
}

基于objc_object結(jié)構(gòu)體,該類的對象的結(jié)構(gòu)體如下:

struct MyObject {
  Class isa; // 成員變量isa持有該類的結(jié)構(gòu)體實例指針
  int val0;  // 原先MyObject類的實例變量val0和val1被直接聲明為成員變量
  int val1;
}

MyObject類的實例變量val0和val1被直接聲明為對象的成員變量。“Objective-C中由類生成對象”意味著,像該結(jié)構(gòu)體這樣“生成由該類生成的對象的結(jié)構(gòu)體實例”。生成的各個對象(即由該類生成的對象的各個結(jié)構(gòu)體實例),通過成員變量isa保持該類的結(jié)構(gòu)體實例指針。


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

各類的結(jié)構(gòu)體是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體:

struct class_t {
  struct class_t *isa;
  struct class_t *superclass;
  Cache cache;
  IMP *vtable;
  uintptr_t data_NEVER_USE;
}

在Objective-C中,比如NSObject的class_t結(jié)構(gòu)體實例以及NSMutableArray的class_t結(jié)構(gòu)體實例等,均生成并保持各個類的class_t結(jié)構(gòu)體實例。
該實例持有聲明的成員變量、方法的名稱、方法的實現(xiàn)(即函數(shù)指針)、屬性以及父類的指針,并被Objective-C運行時庫所使用。

2.3.2 截獲自動變量值

使用Block的時候,不僅可以使用其內(nèi)部的參數(shù),還可以使用Block外部的局部變量。而一旦在Block內(nèi)部使用了其外部變量,這些變量就會被Block保存。
Objective-C代碼:
int main()
{
    int dmy = 256;
    int val = 10;

    const char *fmt = "var = %d\n";

    void (^blk)(void) = ^{
        printf(fmt,val);
    };

    val = 2;
    fmt = "These values were changed. var = %d\n";

    blk();

    return 0;
}
轉(zhuǎn)化而成的C++代碼:
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    const char *fmt; // Block語法表達式“使用的自動變量”被追加到該結(jié)構(gòu)體
    int val;

    // 構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 靜態(tài)函數(shù) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    const char *fmt = __cself->fmt;
    int val = __cself->val;
    /*
    理解:
    1. __main_block_impl_0結(jié)構(gòu)體實例(即Block)所截獲的自動變量在Block語法表達式執(zhí)行之前就被聲明定義,所以,在Objective-C的源代碼中,執(zhí)行Block語法表達式時無需改動便可使用截獲的自動變量值。
    2. "截獲自動變量值"意味著在執(zhí)行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結(jié)構(gòu)體實例(即Block自身)中。
    3. Block不能直接使用“C語言數(shù)組類型的自動變量”,所以,截獲自動變量時,會將其值傳遞給結(jié)構(gòu)體的構(gòu)造函數(shù)進行保存
    */

    printf(fmt, val);
}

// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
    unsigned long reserved;
    unsigned long Block_size;
} __mian_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    // 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)初始化該結(jié)構(gòu)體實例
    void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);

    return 0;
}
  • 在初始化結(jié)構(gòu)體實例時,會根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對由自動變量追加的成員變量進行初始化。即執(zhí)行Block語法使用的自動變量(即截獲的自動變量)fmt和val會初始化結(jié)構(gòu)體實例,被作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中。
__main_block_impl_0結(jié)構(gòu)體如下:
struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 const char *fmt; //截獲的自動變量
 int val;         //截獲的自動變量

 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};

注意1:block沒有使用的自動變量不會被追加,如dmy變量。

注意2: 在初始化block結(jié)構(gòu)體實例時,增加了被截獲的自動變量,block的體積會變大。

  • 執(zhí)行Block語法使用的自動變量fmt和var都是從__cself里面獲取的,更說明了二者是屬于block的。而且從注釋來看(注釋是由clang自動生成的),這兩個變量是值傳遞,而不是指針傳遞,也就是說Block僅僅截獲自動變量的值,所以這就解釋了即使改變了外部的自動變量的值,也不會影響B(tài)lock內(nèi)部的值。
函數(shù)體的代碼如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

 const char *fmt = __cself->fmt; // bound by copy
 int val = __cself->val; // bound by copy
 printf(fmt,val);
}

2.3.3 __block說明符

Block中所使用的被截獲的自動變量就如“帶有自動變量值的匿名函數(shù)”所說,僅截獲自動變量的值。Block中使用自動變量后,在Block結(jié)構(gòu)體實例中重寫該自動變量也不會改變原先截獲的自動變量。
為了在Block中保存值,有兩種方案:
  1. 改變存儲于特殊存儲區(qū)域的變量。
  2. 通過__block修飾符來改變。

1. 改變存儲于特殊存儲區(qū)域的變量

  • 全局變量,可以直接訪問。
  • 靜態(tài)全局變量,可以直接訪問。
  • 靜態(tài)變量,直接指針引用。(不適用)
Objective-C具體的實現(xiàn)的代碼:
int global_val = 1;//全局變量
static int static_global_val = 2;//全局靜態(tài)變量
int main()
{
   static int static_val = 3;//靜態(tài)變量

   void (^blk)(void) = ^{
       global_val *=1;
       static_global_val *=2;
       static_val *=3;
   };
   return 0;
}
轉(zhuǎn)換成C++的代碼:
int global_val = 1; //全局變量
static int static_global_val = 2; // 靜態(tài)全局變量

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *static_val; // 原先的源代碼中使用的靜態(tài)變量被追加為成員變量

    // 構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 靜態(tài)函數(shù) __main_block_func_0 (Block語法表達式發(fā)生的轉(zhuǎn)換)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    int *static_val = __cself->static_val;

    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;

    /*
     1. 使用靜態(tài)變量static_val的指針對靜態(tài)變量進行訪問
     2. 將靜態(tài)變量static_val的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,這是超出作用域,使用靜態(tài)變量的最簡單方法
     */

}

// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
    unsigned long reserved;
    unsigned long Block_size;
} __mian_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

// 主函數(shù),從這里開始閱讀源代碼
int main()
{
    static int static_val = 3; // 靜態(tài)變量

    // 調(diào)用__main_block_impl_0結(jié)構(gòu)體實例的構(gòu)造函數(shù),并將靜態(tài)變量static_val的指針作為參數(shù)傳遞給構(gòu)造函數(shù)
    blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, &static_val);

    return 0;
}
  • 全局變量和全局靜態(tài)變量沒有被截獲到block里面,它們的訪問是不經(jīng)過block的(與__cself無關(guān)):
// 靜態(tài)函數(shù) __main_block_func_0 (Block語法表達式發(fā)生的轉(zhuǎn)換)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    int *static_val = __cself->static_val;

    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;

    /*
     1. 使用靜態(tài)變量static_val的指針對靜態(tài)變量進行訪問
     2. 將靜態(tài)變量static_val的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,這是超出作用域,使用靜態(tài)變量的最簡單方法
     */

}
  • 訪問靜態(tài)變量(static_val)時,將靜態(tài)變量的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存:
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *static_val; // 原先的源代碼中使用的靜態(tài)變量被追加為成員變量

    // 構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

由上述可知, 超出作用域使用指針訪問靜態(tài)變量的這種方法并不適用于自動變量的訪問。

實際上,在由Block語法生成的值Block上,可以存有超過其變量作用域的被截獲對象的自動變量。變量作用域結(jié)束的同時,原來的自動變量被廢棄,因此Block中超過變量作用域而存在的變量,將不能通過指針訪問原來的自動變量。

2. 通過__block修飾符來改變。

__block說明符用于指定將變量值設(shè)置到哪個存儲區(qū)域中,也就是說,當自動變量加上__block說明符之后,會改變這個自動變量的存儲區(qū)域。

__block說明符用來指定Block中想變更值的自動變量,加上__block之后的變量稱之為__block變量

Objective-C中__block變量具體的實現(xiàn)的代碼:
int main()
{
    __block int val = 10;

    void (^blk)(void) = ^{
        val = 1;
    };
    return 0;
}
轉(zhuǎn)換而成的C++代碼:
// 結(jié)構(gòu)體 __Block_byref_val_0
struct __Block_byref_val_0 {
    // 成員變量
    void *__isa;
    __Block_byref_val_0 *__forwarding;
    int __flags;
    int __size;
    int val;  // 相當于原自動變量的成員變量
};

// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_val_0 *val; //“持有相當于原自動變量的成員變量”的“__main_block_impl_0結(jié)構(gòu)體實例”被追加到成員變量中

    // 構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags =0) : val(_val->__forwarding){
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 靜態(tài)函數(shù) __main_block_func_0 (Block語法表達式發(fā)生的轉(zhuǎn)換)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    __Block_byref_val_0 *val = __cself->val;

    (val->__forwarding->val) = 1;
    /*
    1. Block的__main_block_impl_0結(jié)構(gòu)體實例持有指向“__block變量的__Block_byref_val_0結(jié)構(gòu)體實例”的指針(即__Block_byref_val_0 *val)
    2. __Block_byref_val_0結(jié)構(gòu)體實例的成員變量__forwarding持有指向”該實例自身“的指針
    3. 因此,通過__Block_byref_val_0結(jié)構(gòu)體實例的成員變量__forwarding可以訪問該結(jié)構(gòu)體實例的成員變量val
    */
}

// 靜態(tài)函數(shù) __main_block_copy_0
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src){
    _Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}

// 靜態(tài)函數(shù) __main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0*src){
    _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}

// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __mian_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};

// 主函數(shù)
int main()
{
 __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
        0,
        &val,
        0,
        sizeof(__Block_byref_val_0),
        10
    };
    /*
    1. __block變量會變成__Block_byref_val_0結(jié)構(gòu)體類型的自動變量(即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實例)。
    2. 該自動變量被初始化為10,這個值也出現(xiàn)在結(jié)構(gòu)體實例的初始化中,意味著該結(jié)構(gòu)體持有相當于原自動變量的成員變量。
    */

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    return 0;
}
  • 結(jié)構(gòu)體__main_block_impl_0里面增加了一個成員變量,它是一個結(jié)構(gòu)體指針,指向了__Block_byref_val_0結(jié)構(gòu)體的一個實例。

注意1: __Block_byref_val_0結(jié)構(gòu)體并不在__main_block_impl_0結(jié)構(gòu)體中,目的是為了使得多個Block中使用 __block變量。


注意2:__Block_byref_val_0結(jié)構(gòu)體這個結(jié)構(gòu)體是變量val在被__block修飾后生成的。

結(jié)構(gòu)體__main_block_impl_0的聲明:
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_val_0 *val; //“持有相當于原自動變量的成員變量”的“__main_block_impl_0結(jié)構(gòu)體實例”被追加到成員變量中

    // 構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags =0) : val(_val->__forwarding){
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 結(jié)構(gòu)體__Block_byref_val_0兩個成員變量需要特別注意:
  1. val:保存了最初的val變量,也就是說原來單純的int類型的val變量被__block修飾后生成了一個結(jié)構(gòu)體。這個結(jié)構(gòu)體其中一個成員變量持有原來的val變量。
  2. __forwarding:通過__forwarding,可以實現(xiàn)無論__block變量配置在棧上還是堆上都能正確地訪問__block變量,也就是說__forwarding是指向自身的。
結(jié)構(gòu)體__Block_byref_val_0的聲明:
// 結(jié)構(gòu)體 __Block_byref_val_0
struct __Block_byref_val_0 {
    // 成員變量
    void *__isa;
    __Block_byref_val_0 *__forwarding;
    int __flags;
    int __size;
    int val;  // 相當于原自動變量的成員變量
};
__forwarding成員變量的實現(xiàn) :
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 最初,block變量在棧上時,它的成員變量forwarding指向棧上的__block變量結(jié)構(gòu)體實例。

  • 在__block被復(fù)制到堆上時,會將forwarding的值替換為堆上的目標block變量用結(jié)構(gòu)體實例的地址。而在堆上的目標block變量自己的forwarding的值就指向它自己。

2.3.4 Block存儲域

Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量,__block變量轉(zhuǎn)換為 __block的結(jié)構(gòu)體類型的自動變量。
所謂結(jié)構(gòu)體類型的自動變量,即棧上生成的該結(jié)構(gòu)體的實例。而Block共有三種類型。

Block與__block變量的實質(zhì):
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
Block的類有三種:
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

注意:在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。

三種Block在內(nèi)存中的位置:
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

全局Block:_NSConcreteGlobalBlock

Block為_NSConcreteGlobalBlock類對象(即Block配置在程序的數(shù)據(jù)區(qū)域中)的情況有兩種:

  • 記述全局變量的地方有Block語法時??:
void (^blk)(void) = ^{printf("Global Block\n");};
int main()
{
   blk();
}
這里通過clang轉(zhuǎn)換成的C++代碼中Block結(jié)構(gòu)體的聲明是:
struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;//全局
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

Block結(jié)構(gòu)體構(gòu)造函數(shù)里面isa指針被賦予的是&_NSConcreteGlobalBlock,說明它是一個全局Block。

  • Block語法表達式中不使用“應(yīng)截獲的自動變量”時

??:

int(^block)(int count) = ^(int count) {
        return count;
    };
 block(2);
這里通過clang轉(zhuǎn)換成的C++代碼中Block結(jié)構(gòu)體的聲明是:
struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = & _NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

Block結(jié)構(gòu)體構(gòu)造函數(shù)里面isa指針被賦予的是& _NSConcreteStackBlock

這里引用巧神的一段話:由于 clang 改寫的具體實現(xiàn)方式和 LLVM 不太一樣,并且這里沒有開啟 ARC。所以這里我們看到 isa 指向的還是_NSConcreteStackBlock。但在 LLVM 的實現(xiàn)中,開啟 ARC 時,block 應(yīng)該是 _NSConcreteGlobalBlock 類型

棧Block:_NSConcreteStackBlock

  • 在生成Block以后,如果這個Block不是全局Block,那么它就是為_NSConcreteStackBlock對象。
  • 配置在全局區(qū)的block,從變量作用域外也可以通過指針安全地使用。但是設(shè)置在棧上的block,如果其作用域結(jié)束,該block就被銷毀。
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 同樣的,由于__block變量也配置在棧上,如果其作用域結(jié)束,則該__block變量也會被銷毀。

堆Block:_NSConcreteMallocBlock

但是,如果Block變量和__block變量復(fù)制到了堆上以后,則不再會受到變量作用域結(jié)束的影響了,因為它變成了堆Block。


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

將棧block復(fù)制到堆以后,block結(jié)構(gòu)體的isa成員變量變成了_NSConcreteMallocBlock。

__block變量的結(jié)構(gòu)體成員變量__forwarding可以實現(xiàn)無論__block變量配置在棧上還是堆上時都能夠正確地訪問__block變量。
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
ARC有效時,大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實現(xiàn):
  • block作為函數(shù)值返回的時候
typedef int (^blk_t)(int);
blk_t func(int rate)
{
  return ^(int count){return rate * count;};
}

該源代碼中的函數(shù)會返回配置在棧上的Block。即當程序執(zhí)行從該函數(shù)返回函數(shù)調(diào)用方時,變量作用域結(jié)束,因此棧上的Block也被廢棄。但該源代碼通過對應(yīng)ARC的編譯器可轉(zhuǎn)換如下:

blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);

  /*
    * ARC有效時,blk_t tmp 相當于blk_t __strong tmp.
   * 將通過Block語法生成的Block(即配置在棧上的Block結(jié)構(gòu)體實例)
   * 賦值給相當于Block類型的變量tmp
   */

  tmp = objc_retainBlock(tmp);

  /*
   * objc_retainBlock實際上是_Block_copy函數(shù)
   * 將棧上的Block復(fù)制到堆上
   * 復(fù)制后,將堆上的地址作為指針賦值給變量tmp
   */

  return objc_autoreleaseReturnValue(tmp);

  /*
   * 將堆上的Block作為Objective-C對象
   * 注冊到autoreleasepool中,然后返回該對象
   */
}
  • 部分情況下向方法或函數(shù)中傳遞block的時候
    • Cocoa框架的方法而且方法名中含有usingBlock等時。
  • Grand Central Dispatch 的API。
除了以上情況,需要我們手動復(fù)制block。

2.3.5 __block變量存儲域

  • 任何一個block被復(fù)制到堆上時,__block變量也會一并從棧復(fù)制到堆上,并被該Block持有。
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 如果接著有其他Block被復(fù)制到堆上的話,被復(fù)制的Block會持有__block變量,并增加block的引用計數(shù)。
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 如果Block被廢棄,它所持有的__block也就被釋放(不再有block引用它)。
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

2.3.6 截獲對象

首先,我們來看一下生成并持有NSMutableArray類的對象的代碼??:
{
  id array = [[NSMutableArray alloc] init];
}

變量作用域結(jié)束的同時,附有__strong修飾符的變量array被立即釋放并廢棄

而在block里截獲array對象的代碼??:
blk_t blk;
{
    id array = [NSMutableArray new];
    blk = [^(id object){
        [array addObject:object];
        NSLog(@"array count = %ld",[array count]);

    } copy];
}

blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);

其輸出:

 array count = 1
 array count = 2
 array count = 3

賦值給變量array的NSMutableArray類的對象在Block的執(zhí)行部分超出其變量作用域而存在。即array超過了其作用域存在。

通過clang轉(zhuǎn)換成的C++代碼中Block結(jié)構(gòu)體的聲明:
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    id __strong array; 
    // 構(gòu)造函數(shù)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong_array, int flags =0) : array(_array){
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

被NSMutableArray類對象并被截獲的自動變量array,是附有__strong修飾符的成員變量。在Objective-C中,C語言結(jié)構(gòu)體不能含有附有__strong修飾符的變量。因為編譯器不知道何時進行C語言結(jié)構(gòu)體的初始化和廢棄操作,不能很好地管理內(nèi)存。


但是,Objective-C的運行時庫能準確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時機,因此Block的結(jié)構(gòu)體即時含有附有__stong修飾符或__weak修飾符的變量,也可以恰當?shù)剡M行初始化和廢棄。

在實現(xiàn)上是通過__main_block_copy_0函數(shù)和__main_block_dispose_0函數(shù)進行的:
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __mian_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};
  1. __main_block_copy_0函數(shù)(copy函數(shù))和__main_block_dispose_0函數(shù)(dispose函數(shù))指針被賦值__main_block_desc_0結(jié)構(gòu)體成員變量copy和dispose中,但是在轉(zhuǎn)換后的源代碼中,這些函數(shù)包括使用指針全都沒有被調(diào)用。
  1. 而是,在Block從棧復(fù)制到堆時以及堆上的Block被廢棄時會調(diào)用這些函數(shù)。
調(diào)用copy函數(shù)和dispose函數(shù)的時機
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

2.3.7 __block變量和對象

__block可以指定任何類型的自動變量。下面指定用于賦值Objective-C對象的id類型自動變量:

__block id obj = [[NSObject alloc] init];

等同于:

 // ARC有效時,id類型以及對象類型變量默認附加__strong修飾符。
__block id __strong obj = [[NSObject alloc] init];

由clang轉(zhuǎn)換成C++代碼:

/* __block變量的結(jié)構(gòu)體部分 */

// 結(jié)構(gòu)體 __Block_byref_obj_0
struct __Block_byref_obj_0 {
    void *__isa;
    __Block_byref_obj_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose_)(void*);
    __strong id obj; // __block變量被追加為成員變量
};

// 靜態(tài)函數(shù) __Block_byref_id_object_copy_131
static void __Block_byref_id_object_copy_131(void *dst, void *src){
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

// 靜態(tài)函數(shù) __Block_byref_id_object_dispose_131
static void __Block_byref_id_object_dispose_131(void *src){
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
/* __block變量聲明部分 */

__Block_byref_obj_0 obj = {
    0,
    &obj,
    0x20000000,
    sizeof(__Block_byref_obj_0),
    __Block_byref_id_object_copy_131,
    __Block_byref_id_object_dispose_131,
    [[NSObject alloc] init]
};
  • 在Block中使用“附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞俊钡那闆r下:
  • 當Block從棧復(fù)制到堆時,使用_Block_object_copy函數(shù),持有Block截獲的對象。

  • 當堆上的Block被廢棄時,使用_Block_object_dispose函數(shù),釋放Block截獲的對象。

  • 在__block變量為“附有 __strong修飾符的id類型或?qū)ο箢愋妥詣幼兞俊钡那樾蜗聲l(fā)生同樣的過程。
  • 當__block變量從棧復(fù)制到堆時,使用_Block_object_copy函數(shù),持有賦值給__block變量的對象。

  • 當堆上的__block變量被廢棄時,使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對象。

由此可知:只要__block變量在堆上繼續(xù)存在,那么該對象就會繼續(xù)處于被持有的狀態(tài)。這與在Block中,對象賦值給“附有__strong修飾符的對象類型自動變量”相同。
  • 對象賦值給“附有__weak修飾符的id類型或?qū)ο箢愋蚠_block自動變量”時,__block變量對該對象持有弱引用。此時nil賦值在自動變量上。
  • 在使用附有__unsafe_unretained修飾符的變量時,注意不要通過懸掛指針訪問已被廢棄的對象,否則程序可能會崩潰!
  • __autoreleasing修飾符與__block說明符同時使用會產(chǎn)生編譯錯誤。

2.3.8 Block循環(huán)引用

如果在Block內(nèi)部使用__strong修飾符的對象類型的自動變量,那么當Block從棧復(fù)制到堆的時候,該對象就會被Block所持有。

何時棧上的Block會復(fù)制到堆:
  • 調(diào)用Block的copy實例方法時
  • Block作為函數(shù)返回值返回時
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時

如果這個對象還同時持有Block的話,就容易發(fā)生循環(huán)引用。

  • 使用Block類型成員變量和附有__strong修飾符的self出現(xiàn)循環(huán)引用
typedef void(^blk_t)(void);

@interface Person : NSObject
{
    blk_t blk_;
}

@implementation Person

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"self = %@",self);
    };
    return self;
}

@end

Block blk_t持有self,而self也同時持有作為成員變量的blk_t


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • Block中沒有使用self,但是截獲了self
@interface MyObject : NSObject
{
    blk_t blk_;
    id obj_;
}
@end

@implementation MyObject
- (id)init
{
    self = [super init];

    blk_ = ^{NSLog(@"obj_ = %@", obj_);};

    return self;
}

Block語法中使用的obj_實際上截獲了self,而對編譯器來說,obj_只不過是對象的結(jié)構(gòu)體的成員變量。

blk_ = ^{NSLog(@"obj_ = %@", self->obj_);};
有兩種方法來解決Block循環(huán)引用:
  • 使用__block變量避免循環(huán)引用
  • 使用Block類型成員變量和附有__weak修飾符的對象(通常是self)避免循環(huán)引用
使用__block變量避免循環(huán)引用
typedeft void (^blk_t)(void);

@interface MyObject : NSObject
{
    blk_t blk_;
}
@end

@implementation MyObject
- (id)init
{
    self = [super init];

    __block id blockSelf = self;

    blk_ = ^{
          NSLog(@"self = %@", blockSelf);
          blockSelf = nil; // 記得清零
        };

    return self;
}

- (void)execBlock
{
    blk_();
}

- (void)dealloc
{
    NSLog(@"dealloc");
}
@end

int main()
{
    id o = [[MyObject alloc] init];

    [o execBlock];

    return 0;
}

如果不調(diào)用execBlock實例方法(即不執(zhí)行賦值給成員變量blk_的Block),便會循環(huán)引用并引起內(nèi)存泄露。


使用__block變量不恰當會出現(xiàn)循環(huán)引用

在生成并持有對象的狀態(tài)下會引起以下循環(huán)引用


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

如果不執(zhí)行execBlock實例方法,就會持續(xù)該循環(huán)引用從而造成內(nèi)存泄露。

通過執(zhí)行execBlock實例方法,Block被執(zhí)行,nil被賦值在__block變量blockSelf中,__block變量blockSelf對MyObject類對象的強引用失效,從而避免了循環(huán)引用


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

使用__block變量避免循環(huán)引用的優(yōu)缺點
優(yōu)點:

  • 通過__block變量可控制對象的持有期間
  • 在不能使用__weak修飾符的環(huán)境中使用

缺點:

  • 為避免循環(huán)引用必須執(zhí)行Block
使用Block類型成員變量和附有__weak修飾符的對象(通常是self)避免循環(huán)引用
- (id)init
{
    self = [super init];

    __weak __typeof(&*self)weakSelf = self;

    blk_ = ^{NSLog(@"self = %@", weakSelf);};

    return self;
}
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

若由于Block引發(fā)了循環(huán)引用時,在ARC有效時,使用__weak修飾符來避免Block中的循環(huán)引用。ARC無效時,__block說明符被用來避免Block中的循環(huán)引用。

總結(jié)

Blocks是“帶有自動變量(即局部變量)的匿名函數(shù)”。實質(zhì)是Objective-C對閉包的對象實現(xiàn),簡單說來,Block就是對象。
在Blocks中,Block常量表達式會截獲所使用的自動變量的值(即保存該自動變量的瞬間值)。附有 __block說明符的自動變量可在Block中賦值。
棧上的Block會復(fù)制到堆時,Block上的__block變量跟著復(fù)制到堆上。只要__block變量在堆上繼續(xù)存在,那么對象就會繼續(xù)處于被持有的狀態(tài)。
ARC有效時,大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實現(xiàn):
  • block作為函數(shù)值返回的時候
  • 部分情況下向方法或函數(shù)中傳遞block的時候:
  • Cocoa框架的方法而且方法名中含有usingBlock等時。
  • Grand Central Dispatch 的API。
若由于Block引發(fā)了循環(huán)引用時,在ARC有效時,使用__weak修飾符來避免Block中的循環(huán)引用。ARC無效時,__block說明符被用來避免Block中的循環(huán)引用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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