Blocks

一. Blocks的認(rèn)識(shí)

什么是Blocks?

Blocks是C語言的拓展功能(帶有局部變量的匿名函數(shù))。局部變量跟block的關(guān)系就像成員變量和類。
所謂匿名函數(shù),就是不帶名稱的函數(shù)。C語言的標(biāo)準(zhǔn)是不存在這樣的函數(shù)的。

//聲明一個(gè)函數(shù)
int  func( int  count);

//直接調(diào)用函數(shù)時(shí)
int result = func(10);
//用函數(shù)指針調(diào)用函數(shù)時(shí)
int result =(* func)(10);

在C語言中,不使用想賦值的函數(shù)的名稱,就無法取得該函數(shù)的地址。而通過Blocks,就能夠使用不帶名稱的函數(shù)。

Block的語法

完整的Block語法與C語言函數(shù)相比有兩點(diǎn)不同:沒有函數(shù)名和帶有“^”。

^ 返回值類型 參數(shù)列表 表達(dá)式

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

但是Block語法可以省略返回值類型和參數(shù)列表。省略返回值類型時(shí),如果表達(dá)式中有return語句就使用該返回值的類型,如果沒有return語句就使用void類型。


Block語法省略返回值類型和參數(shù)列表.png
//省略后的Block
^{printf("Blocks\n");}
Blocks的使用

我們先來看看C語言函數(shù)的使用:

int func(int count){
  return count + 1;
}
//將函數(shù)地址賦值給函數(shù)指針類型變量
int (*funcptr)(int) = &func;

聲明Block類型變量以及變量之間的賦值:

int (^blk)(int)  = ^(int count){return count + 1};
int (^blk1)(int);
blk1 = blk;

Block類型變量作為函數(shù)參數(shù)和函數(shù)返回值:

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

使用typedef聲明Block類型變量

typedef int (^blk_t)(int);
int func(blk_t blk){
  return blk(10);
}

也可以像C語言變量一樣使用,使用Block的指針類型變量。

typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1};
blk_t *blkptr = &blk;
(*blkptr)(10);
Block截獲的自動(dòng)變量值

Block表達(dá)式截獲所使用的自動(dòng)變量的值是該自動(dòng)變量的瞬間值,不能在Block中改變(編譯器會(huì)報(bào)錯(cuò)),只能輸出該瞬間值。如果在Block中修改該值,要加上__block修飾符。

int let = 10;
__block int var = 20;
void (^blk)(void) = ^{
  printf("%d,%d",let,var);  //輸出結(jié)果 10,21
  var = 22;                //這邊也可以改變var的值,而let不可以
};
let = 11;      
var = 21;
blk();

二. Block的底層實(shí)現(xiàn)

通過clang(LLVM編譯器)將Block語法的源代碼轉(zhuǎn)換為C++源代碼來了解Block的底層實(shí)現(xiàn)。

  • 在Xcode創(chuàng)建Block.m文件
  • 打開終端,cd到存放Block.m的文件夾
  • 輸入clang -rewrite-objc Block.m,生成Block.cpp
int mian(){
    void (^blk)(void) = ^{
        printf("Block");
    };
    blk();
    return 0;
}

打開.cpp拉到最后面,這些才是我們轉(zhuǎn)換后的源碼:

struct __block_impl {
  void *isa;      //對象都有的isa指針,指向元類
  int Flags;      //標(biāo)志
  int Reserved;   //今后版本升級(jí)所需的區(qū)域
  void *FuncPtr;  //函數(shù)指針
};

/* block結(jié)構(gòu)體 */
struct __mian_block_impl_0 {
   struct __block_impl impl;           
   struct __mian_block_desc_0* Desc;  
 
   // block構(gòu)造函數(shù) 
   __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
     impl.isa = &_NSConcreteStackBlock;   
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
  }
};

/* block中的代碼 */
//__cself相當(dāng)于Objective-C中的self(用OC類的說法就是self 執(zhí)行__mian_block_func_0)
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
        printf("Block");
 }

static struct __mian_block_desc_0 {
  size_t reserved;     //今后版本升級(jí)所需的區(qū)域
  size_t Block_size;   //Block的大小
} __mian_block_desc_0_DATA = {     //這部分為賦值
  0, 
  sizeof(struct __mian_block_impl_0)
};

/*  mian函數(shù)  */
int mian(){
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

我們先看看main函數(shù)里面的代碼,我們可簡化成以下代碼:

//創(chuàng)建Block
struct __mian_block_impl_0  temp = __mian_block_impl_0(__mian_block_func_0, &__mian_block_desc_0_DATA));
struct __mian_block_impl_0  *blk = &tmp;

//執(zhí)行Block
(*blk->impl.FuncPtr)(blk);

該源代碼將__mian_block_impl_0結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的__mian_block_impl_0結(jié)構(gòu)體實(shí)例的指針,賦值給__mian_block_impl_0結(jié)構(gòu)體指針類型的變量blk。通俗地講,就是將__mian_block_impl_0結(jié)構(gòu)體實(shí)例的指針賦給變量blk。
__mian_block_impl_0構(gòu)造函數(shù)(即源代碼中的Block構(gòu)造函數(shù))的第一個(gè)參數(shù)是Block語法轉(zhuǎn)換的C語言指針,第二個(gè)參數(shù)是作為靜態(tài)全局變量初始化的__mian_block_desc_0結(jié)構(gòu)體實(shí)例指針。
我們再來看執(zhí)行Block的部分,這是簡單地使用函數(shù)指針調(diào)用函數(shù)。在構(gòu)造函數(shù)中,由Block語法轉(zhuǎn)換的__mian_block_func_0函數(shù)的指針被賦值給成員變量FuncPtr,并以Block自身為參數(shù)。
補(bǔ)充重要的一點(diǎn),前面提到的:

 impl.isa = &_NSConcreteStackBlock;    

其實(shí),block就是Objective-C對象。這邊isa指針指向的_NSConcreteStackBlock,該Block的信息放置于_NSConcreteStackBlock中(相當(dāng)于子類和父類的關(guān)系)。

截獲自動(dòng)變量值

我們先來看看截獲自動(dòng)變量值的源代碼,理解自動(dòng)變量值為什么只是一個(gè)瞬間值。
Objective-C代碼:

int mian(){
    int val = 10;
    void (^blk)(void) = ^{
        printf("val = %d",val); //輸出結(jié)果 val= 10
    };
    val = 11;
    blk();
    return 0;
}

clang后的源代碼:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

/ * Block結(jié)構(gòu)體 */
struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  int val;            //比之前的結(jié)構(gòu)多出該成員變量val
  /* 構(gòu)造函數(shù) */
  __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/ * Block中執(zhí)行的函數(shù) */
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
    int val = __cself->val;     // bound by copy
    printf("val = %d",val);     //打印的是Block自己的成員變量的值
}

static struct __mian_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};

int mian(){
    int val = 10;

    //構(gòu)造函數(shù)中傳入val的值
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, val));

    val = 11;

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

通過源碼,我們看出,Block語法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到__mian_block_impl_0結(jié)構(gòu)體中,并且會(huì)從構(gòu)造函數(shù)中傳入val的值作為參數(shù)。改寫該值報(bào)編譯錯(cuò)誤的原因我們也由想而知。

struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  int val;            //比之前的結(jié)構(gòu)多出該成員變量val
  /* 構(gòu)造函數(shù) */
  __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在后面的輸出函數(shù)中,也是輸出Block的成員變量val的值。由此可見,在__mian_block_impl_0結(jié)構(gòu)體實(shí)例中,自動(dòng)變量值被截獲。

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
    int val = __cself->val;     // bound by copy
    printf("val = %d",val);     //打印的是Block自己的成員變量的值
}
__blcok說明符的值

那如何才能改變Block中截獲的自動(dòng)變量?有兩種方法:
① C語言的靜態(tài)變量、靜態(tài)全局變量、全局變量允許Block改寫值

int global_val = 1; //全局變量
static int static_global_val = 2; //靜態(tài)全局變量

int mian(){
    static int static_val = 10;  //靜態(tài)變量
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *=3;
    };
    blk();
    return 0;
}

轉(zhuǎn)換后的源碼為:

int global_val = 1;
static int static_global_val = 2;

struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  int *static_val;    //保存的是靜態(tài)變量的指針
  __mian_block_impl_0(void *fp, struct __mian_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;
  }
};

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy
        global_val *= 1;
        static_global_val *= 2;
        (*static_val) *=3;
    }

static struct __mian_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};

int mian(){
    static int static_val = 10;
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

對靜態(tài)全局變量static_global_val和全局變量global_val的訪問和轉(zhuǎn)換前完全相同。
而靜態(tài)變量static_val,是使用其指針進(jìn)行訪問的。將靜態(tài)變量static_val的指針傳遞給__mian_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存。那為什么自動(dòng)變量不用指針訪問的方法呢?因?yàn)樽詣?dòng)變量存放在棧幀中,變量作用域結(jié)束的同時(shí),自動(dòng)變量被廢棄,Block將不能用指針訪問到原來的自動(dòng)變量。

② 使用__block說明符
__block說明符類似于static、auto和register說明符,它們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中。例如,auto表示作為自動(dòng)變量存儲(chǔ)在棧中,static表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中。

int mian(){
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    };
    blk();
    return 0;
}

轉(zhuǎn)換后的源代碼:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

/* __block變量生成的結(jié)構(gòu)體 */
struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;  
 int __flags;
 int __size;
 int val;     //保存的val的值
};

struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref

  __mian_block_impl_0(void *fp, struct __mian_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;
  }
};

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
}

static void __mian_block_copy_0(struct __mian_block_impl_0*dst, struct __mian_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __mian_block_dispose_0(struct __mian_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __mian_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __mian_block_impl_0*, struct __mian_block_impl_0*);
  void (*dispose)(struct __mian_block_impl_0*);
} __mian_block_desc_0_DATA = {
   0, 
  sizeof(struct __mian_block_impl_0), 
  __mian_block_copy_0, 
  __mian_block_dispose_0
};

int mian(){
    //創(chuàng)建__Block_byref_val_0結(jié)構(gòu)體實(shí)例
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
 
    //將__Block_byref_val_0結(jié)構(gòu)體指針作為參數(shù)
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

加上__block說明符,我們發(fā)現(xiàn)__block變量同Block一樣變成__Block_byref_val_0結(jié)構(gòu)體類型的自動(dòng)變量。__Block_byref_val_0結(jié)構(gòu)體的地址作為參數(shù)傳入Block的構(gòu)造函數(shù)中。因此,Block的__mian_block_impl_0結(jié)構(gòu)體實(shí)例持有指向__block變量的__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針。
至于__Block_byref_val_0結(jié)構(gòu)體為什么在Block外創(chuàng)建,是為了方便多個(gè)Block使用。

struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;  //指向自身的指針
 int __flags;
 int __size;
 int val;         //保存的val的值
};

在給__bloc變量賦值的源碼中:

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {

    __Block_byref_val_0 *val = __cself->val; // bound by ref
     (val->__forwarding->val) = 1;
}

__Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding指向該實(shí)例自身的指針。通過成員變量__forwarding訪問成員變量val。(這邊有點(diǎn)繞,要弄懂先要弄清楚Block的存儲(chǔ)域)

訪問__block變量.png

Block的存儲(chǔ)域

Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動(dòng)變量,__block變量轉(zhuǎn)換為__block變量的結(jié)構(gòu)體類型的自動(dòng)變量。所謂結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例。那Block超出變量作用域是如何繼續(xù)存在的呢?
要了解這點(diǎn),我們要先知道Block的三種類型以及它們的存儲(chǔ)域:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock
不同類型Block的存儲(chǔ)域.png

通過聲明全局變量blk來使用Block語法,那該Block類為_NSConcreteGlobalBlock。因?yàn)樵谑褂萌肿兞康牡胤讲荒苁褂米詣?dòng)變量,所以不存在對自動(dòng)變量的截獲。因此將Block用結(jié)構(gòu)體實(shí)例設(shè)置在與全局變量相同的數(shù)據(jù)區(qū)域中即可。另外,在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時(shí),只要Block不截獲自動(dòng)變量,就可以將Block用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域。
綜上,以下情況Block為_NSConcreteGlobalBlock類對象,除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象,且設(shè)置在棧上。

  • 記述全局變量的地方有Block語法時(shí)
  • Block語法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)

    配置在全局變量的Block,從變量作用域外也可以通過指針安全地使用。但設(shè)置在棧上的Block,如果所屬的變量作用域結(jié)束,該Block和__block變量都會(huì)被廢棄。針對這個(gè)問題,Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法。
    從棧復(fù)制到堆上的Block與__block變量.png
typedef int (^blk_t)(int)

blk_t func(int rate){
  return ^(int count){return rate * count};
}
blk_t func(int rate){
  blk_t tmp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA,rate);
/*
 * 將配置在棧上的Block的結(jié)構(gòu)體實(shí)例賦值給tmp
 */

  tmp = objc_retainBlock(tmp);  
/*
 * 等價(jià)于tmp = _Block_copy(tmp)
 * 將棧上的Block賦值到堆上,并將堆上的地址作為指針賦值給tmp
 */

  return objc_autoreleaseReturnValue(tmp);
 /*
  * 將堆上的Block對象注冊到autoreleasepool并返回
  */
}

將Block作為函數(shù)返回值時(shí),編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。當(dāng)向方法或函數(shù)的參數(shù)中傳遞Block時(shí),需要我們手動(dòng)生成代碼,調(diào)用Block的copy方法。

Block的Copy.png

不過以下方法或函數(shù)不用手動(dòng)調(diào)用:

  • Cocoa框架的方法且方法名中含有usingBlock等時(shí)(比如NSArray類的enumerateObjectsUsingBlock實(shí)例方法)
  • GCD的API

當(dāng)使用__block變量的Block從棧上復(fù)制到堆上時(shí),這些__block變量也全部被從棧復(fù)制到堆,Block持有__block變量。

Block持有__blcok變量.png
Block的廢棄和__block變量的釋放.png

使用__block變量的Block持有__block變量(__block變量作為Block的一個(gè)成員變量)。如果Block被廢棄,它所持有的__block變量也就被釋放。棧上的__block用結(jié)構(gòu)體實(shí)例在從棧復(fù)制到堆時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制目標(biāo)堆上的__block用結(jié)構(gòu)體實(shí)例的地址。這樣不管__block變量配置在棧上還是堆上,都可以正確訪問該變量。

復(fù)制__block變量.png
__block int val = 0;
void (^blk)(void) = [^{++val;}  copy];
++val;
blk();

兩個(gè)++val均可轉(zhuǎn)換成以下源碼:

++(val.__forwarding->val);

第一個(gè)val為復(fù)制到堆上的__block變量用結(jié)構(gòu)體實(shí)例,會(huì)通過指針訪問到自己。第二個(gè)val為復(fù)制前棧上__block變量用結(jié)構(gòu)體實(shí)例,通過指針訪問到堆上的__block變量用結(jié)構(gòu)體實(shí)例。通過該功能,都可以順利訪問同一個(gè)__block變量。

截獲對象

前面我們截獲的是普通值或帶__block說明符的值,那要是在Block語法中使用對象呢?

typedef void(^blk_t)(id obj);
blk_t blk;
void setupBlk(){
    id array = [[NSMutableArray alloc] init];
    blk = [^(id obj){
        [array addObject:obj];
        NSLog(@"array count = %ld",[array count]);
    } copy];
}
int main(){
    setupBlk();
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    return 0;
}

輸出結(jié)果為:

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

本來array的變量域結(jié)束的同時(shí),array被廢棄,其強(qiáng)引用失效,因此賦值給變量array的NSMutableArray類的對象必定被釋放并廢棄。但是代碼運(yùn)行正常,由此可見賦值給array的NSMutableArray類的對象在最后Block的執(zhí)行部分超出其變量作用域而存在。通過編譯器轉(zhuǎn)換后的源代碼如下:

typedef void(*blk_t)(id obj);
blk_t blk;

struct __setupBlk_block_impl_0 {
  struct __block_impl impl;
  struct __setupBlk_block_desc_0* Desc;
  id array;
  /* 構(gòu)造方法 */
  __setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __setupBlk_block_func_0(struct __setupBlk_block_impl_0 *__cself, id obj) {
        id array = __cself->array; // bound by copy
        ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9v_3rmg01ds0zj0gpwn04shlxwc0000gn_T_Block_097da3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
    }

static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src) {
  //相當(dāng)于retain方法
  _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
  //相當(dāng)于release方法
  _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __setupBlk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __setupBlk_block_impl_0*, struct __setupBlk_block_impl_0*);
  void (*dispose)(struct __setupBlk_block_impl_0*);
} __setupBlk_block_desc_0_DATA = {
   0,
   sizeof(struct __setupBlk_block_impl_0),
   __setupBlk_block_copy_0, 
  __setupBlk_block_dispose_0
};

void setupBlk(){
    id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
    blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__setupBlk_block_impl_0((void *)__setupBlk_block_func_0, &__setupBlk_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}

int main(){
    setupBlk();

    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));

    return 0;
}

我們可以發(fā)現(xiàn),Block結(jié)構(gòu)體中截獲__strong修飾符的成員變量array:

struct __setupBlk_block_impl_0 {
  struct __block_impl impl;
  struct __setupBlk_block_desc_0* Desc;
  id array;  //等同于id  __strong array
}

在ARC需要遵守的規(guī)則中,對象型變量不能作為C語言結(jié)構(gòu)體的成員變量。因?yàn)榫幾g器不知道何時(shí)進(jìn)行C語言結(jié)構(gòu)體的初始化和廢棄,不能很好地管理內(nèi)存。但是Objective-C的運(yùn)行時(shí)庫能夠準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時(shí)機(jī),因此Block用結(jié)構(gòu)體中即使含有附有__strong或__weak修飾符的變量,也能恰當(dāng)?shù)剡M(jìn)行初始化和廢棄。
在該Block源碼中,__setupBlk_block_desc_0中增加了copy和dispose函數(shù)指針的成員變量,以及作為指針賦值給該成員變量的__setupBlk_block_copy_0和__setupBlk_block_dipose_0方法。
__setupBlk_block_copy_0函數(shù)調(diào)用相當(dāng)于retain實(shí)例方法的函數(shù),將對象賦值在對象類型的結(jié)構(gòu)體成員變量中,__setupBlk_block_dipose_0函數(shù)調(diào)用相當(dāng)于release實(shí)例方法的函數(shù),釋放賦值在對象類型的結(jié)構(gòu)體成員變量中的對象。簡單說,就是對Block結(jié)構(gòu)體中成員變量array賦值和廢棄。當(dāng)然,在Block從棧復(fù)制到堆時(shí)以及堆上的Block被廢棄時(shí)才會(huì)調(diào)用這些函數(shù)。

如果在在array前面加上__block說明符呢?
轉(zhuǎn)換的源碼如下:

struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

struct __setupBlk_block_impl_0 {
  struct __block_impl impl;
  struct __setupBlk_block_desc_0* Desc;
  __Block_byref_array_0 *array; 

  __setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src{
  _Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
  _Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}

同Block截獲對象一樣,當(dāng)__block變量從棧復(fù)制到堆時(shí),使用_Block_object_assign函數(shù),持有賦值給__block變量的對象。當(dāng)堆上的__block變量被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放賦值給__blcok變量的對象。
由此可見,只要堆上的Block的成員變量(__block變量或者對象)存在,那么該對象就會(huì)繼續(xù)處于被持有狀態(tài)。

如果是__weak和__block的組合呢?
當(dāng)超出作用域時(shí),__weak的對象也會(huì)置于nil。所以會(huì)得出結(jié)果:

array count = 0
array count = 0
array count = 0

如果不調(diào)用copy方法呢?
執(zhí)行該代碼后,程序會(huì)強(qiáng)制結(jié)束。因?yàn)橹挥姓{(diào)用_Block_copy才能持有截獲的附有__strong修飾符的對象類型的自動(dòng)變量值,否則即使截獲了對象,沒有retain,它也會(huì)隨著變量作用域的結(jié)束而被廢棄。
所以,Block中使用對象類型自動(dòng)變量時(shí),除以下情形外,需要調(diào)用Block的copy方法。

  • Block作為函數(shù)返回值返回時(shí)
  • 將Block賦值給類的附有__strong修飾符的id類型或Block類型成員變量
  • 向方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時(shí)

三. Block的循環(huán)引用

如果在Block中使用__strong 修飾符的對象類型自動(dòng)變量,那么當(dāng)Block從棧復(fù)制到堆時(shí),該對象為Block所持有。這樣容易引起循環(huán)引用。

typedef void(^blk_t)(void);
@interface MyObject()
{
    blk_t _blk;
}
@end
@implementation MyObject
- (instancetype)init{
    self = [super init];
    _blk = ^{NSLog(@"self = %@",self);};
    return self;
}
- (void)dealloc{
    NSLog(@"dealloc");
}

//在main函數(shù)創(chuàng)建MyObject
int main(){
    id obj = [[MyObject alloc] init];
    return 0;
}

MyObject類對象的成員變量_blk持有Block的強(qiáng)引用,即MyObject對象持有Block。由于Block是賦值給成員變量_blk,Block會(huì)由棧復(fù)制到堆,并持有所使用的self。所以造成循環(huán)引用。

使用Block成員變量循環(huán)引用.png

那避免循環(huán)引用有哪些方法呢?
① 聲明附有__weak修飾符的變量,并將self賦值使用。

- (instancetype)init{
    self = [super init];
    id __weak tmp = self;
    _blk = ^{NSLog(@"self = %@",tmp);};
    return self;
}
__weak避免循環(huán)引用.png

②使用__block變量(可用于ARC無效時(shí),不過缺點(diǎn)是必須執(zhí)行Block,將臨時(shí)變量tmp置為nil)

- (instancetype)init{
    self = [super init];
    id __block tmp = self;
    _blk = ^{
        NSLog(@"self = %@",tmp);
        tmp = nil;
    };
    return self;
}
__block避免循環(huán)引用.png

還有一點(diǎn)需要注意的時(shí),__block說明符解決循環(huán)引用時(shí)的方式在ARC和MRC是不一樣的。MRC中,Block從棧復(fù)制到堆時(shí),Block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣?dòng)變量,不會(huì)被retain,若沒有附有__block,會(huì)被retain。所以以下可以解決循環(huán)引用。

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

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

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