《Objective-C高級編程》Blocks

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

2.1 Blcoks概要

2.1.1 什么是Blocks

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

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

2.2 Blocks模式

2.2.1 Block語法

  • Block常量表達(dá)式:

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

??:

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

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

省略返回值類型時(shí)日川,如果表達(dá)式有return語句就返回該返回值的類型辣垒。如果表達(dá)式?jīng)]有return語句就返回void類型。如果表達(dá)式有多個(gè)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常量表達(dá)式:

圖片來自:《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語法就相當(dāng)于生成了可賦值給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ù)和返回值時(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 截獲自動(dòng)變量值

Blocks中,Block常量表達(dá)式會(huì)截獲所使用的自動(dòng)變量的值(即保存該自動(dòng)變量的瞬間值)撒妈,從而在執(zhí)行塊時(shí)使用恢暖。
??:

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

2.2.4 __block說明符()

使用附有 __block說明符的自動(dòng)變量可在Block中賦值該變量稱為 __block 變量杰捂。__block說明符也被稱之為存儲(chǔ)類型修改符。
??:

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

2.2.5 截獲的自動(dòng)變量

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

??:

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

2.3 Blocks的實(shí)現(xiàn)

2.3.1 Block的實(shí)質(zhì)

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

Objective-C中Block的實(shí)現(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;      // 標(biāo)志
    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)體實(shí)例的初始化部分
    0,
    sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實(shí)例)的大小進(jìn)行初始化
};

// 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的值的指針央勒,也就相當(dāng)于是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)體有三個(gè)部分:
  • 第一個(gè)是成員變量impl,它是實(shí)際的函數(shù)指針列林,它指向__main_block_func_0瑞你。impl結(jié)構(gòu)體的聲明:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;      // 標(biāo)志
    int Reserved;   // 今后版本升級所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
};
  • 第二個(gè)是成員變量是指向__main_block_desc_0結(jié)構(gòu)體的Desc指針,是用于描述當(dāng)前這個(gè)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)體實(shí)例的初始化部分
    0,
    sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實(shí)例)的大小進(jìn)行初始化
};
  • 第三個(gè)部分是__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)者甲,__main_block_impl_0 就是該 block 的實(shí)現(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;
    }
在這個(gè)結(jié)構(gòu)體的構(gòu)造函數(shù)里:
  • isa指針保持這所屬類的結(jié)構(gòu)體的實(shí)例的指針过牙。
  • __main_block_imlp_0結(jié)構(gòu)體就相當(dāng)于Objective-C類對象的結(jié)構(gòu)體
  • _NSConcreteStackBlock相當(dāng)于Block的結(jié)構(gòu)體實(shí)例
也就是說Block實(shí)質(zhì)是Objective-C對閉包的對象實(shí)現(xiàn),簡單說來纺铭,Block就是對象寇钉。
Objective-C類與對象的實(shí)質(zhì)是什么呢?
Objective-C類與對象的實(shí)質(zhì):
  • Objective-C中的對象就是一個(gè)結(jié)構(gòu)體舶赔,并且所有的對象都有一個(gè)相同的結(jié)構(gòu)體(即Class是類,id是對象)竟纳。而且每一個(gè)對象都有一個(gè)isa指針撵溃,這個(gè)isa指向生成該對象的類。

  • Objective-C類與對象的實(shí)現(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;
} ;

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

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

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

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

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


圖片來自:《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)體實(shí)例以及NSMutableArray的class_t結(jié)構(gòu)體實(shí)例等纺荧,均生成并保持各個(gè)類的class_t結(jié)構(gòu)體實(shí)例。
該實(shí)例持有聲明的成員變量颅筋、方法的名稱宙暇、方法的實(shí)現(xiàn)(即函數(shù)指針)、屬性以及父類的指針垃沦,并被Objective-C運(yùn)行時(shí)庫所使用客给。

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

使用Block的時(shí)候,不僅可以使用其內(nèi)部的參數(shù)肢簿,還可以使用Block外部的局部變量靶剑。而一旦在Block內(nèi)部使用了其外部變量,這些變量就會(huì)被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語法表達(dá)式“使用的自動(dòng)變量”被追加到該結(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)體實(shí)例(即Block)所截獲的自動(dòng)變量在Block語法表達(dá)式執(zhí)行之前就被聲明定義桩引,所以,在Objective-C的源代碼中收夸,執(zhí)行Block語法表達(dá)式時(shí)無需改動(dòng)便可使用截獲的自動(dòng)變量值坑匠。
    2. "截獲自動(dòng)變量值"意味著在執(zhí)行Block語法時(shí),Block語法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中卧惜。
    3. Block不能直接使用“C語言數(shù)組類型的自動(dòng)變量”厘灼,所以,截獲自動(dòng)變量時(shí)咽瓷,會(huì)將其值傳遞給結(jié)構(gòu)體的構(gòu)造函數(shù)進(jìn)行保存
    */

    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)體實(shí)例
    void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);

    return 0;
}
  • 在初始化結(jié)構(gòu)體實(shí)例時(shí)设凹,會(huì)根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對由自動(dòng)變量追加的成員變量進(jìn)行初始化。即執(zhí)行Block語法使用的自動(dòng)變量(即截獲的自動(dòng)變量)fmt和val會(huì)初始化結(jié)構(gòu)體實(shí)例茅姜,被作為成員變量追加到了__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; //截獲的自動(dòng)變量
 int val;         //截獲的自動(dòng)變量

 __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沒有使用的自動(dòng)變量不會(huì)被追加,如dmy變量钻洒。

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

  • 執(zhí)行Block語法使用的自動(dòng)變量fmt和var都是從__cself里面獲取的素标,更說明了二者是屬于block的称诗。而且從注釋來看(注釋是由clang自動(dòng)生成的),這兩個(gè)變量是值傳遞头遭,而不是指針傳遞粪狼,也就是說Block僅僅截獲自動(dòng)變量的值退腥,所以這就解釋了即使改變了外部的自動(dòng)變量的值,也不會(huì)影響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中所使用的被截獲的自動(dòng)變量就如“帶有自動(dòng)變量值的匿名函數(shù)”所說,僅截獲自動(dòng)變量的值享潜。Block中使用自動(dòng)變量后困鸥,在Block結(jié)構(gòu)體實(shí)例中重寫該自動(dòng)變量也不會(huì)改變原先截獲的自動(dòng)變量。
為了在Block中保存值剑按,有兩種方案:
  1. 改變存儲(chǔ)于特殊存儲(chǔ)區(qū)域的變量疾就。
  2. 通過__block修飾符來改變。

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

  • 全局變量艺蝴,可以直接訪問猬腰。
  • 靜態(tài)全局變量,可以直接訪問猜敢。
  • 靜態(tài)變量姑荷,直接指針引用。(不適用)
Objective-C具體的實(shí)現(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語法表達(dá)式發(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)變量進(jìn)行訪問
     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)體實(shí)例的構(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語法表達(dá)式發(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)變量進(jìn)行訪問
     2. 將靜態(tài)變量static_val的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,這是超出作用域博脑,使用靜態(tài)變量的最簡單方法
     */

}
  • 訪問靜態(tài)變量(static_val)時(shí)憎乙,將靜態(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)變量的這種方法并不適用于自動(dòng)變量的訪問叉趣。

實(shí)際上泞边,在由Block語法生成的值Block上,可以存有超過其變量作用域的被截獲對象的自動(dòng)變量君账。變量作用域結(jié)束的同時(shí)繁堡,原來的自動(dòng)變量被廢棄,因此Block中超過變量作用域而存在的變量乡数,將不能通過指針訪問原來的自動(dòng)變量椭蹄。

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

__block說明符用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)區(qū)域中净赴,也就是說绳矩,當(dāng)自動(dòng)變量加上__block說明符之后,會(huì)改變這個(gè)自動(dòng)變量的存儲(chǔ)區(qū)域玖翅。

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

Objective-C中__block變量具體的實(shí)現(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;  // 相當(dāng)于原自動(dòng)變量的成員變量
};

// 結(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; //“持有相當(dāng)于原自動(dòng)變量的成員變量”的“__main_block_impl_0結(jié)構(gòu)體實(shí)例”被追加到成員變量中

    // 構(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語法表達(dá)式發(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)體實(shí)例持有指向“__block變量的__Block_byref_val_0結(jié)構(gòu)體實(shí)例”的指針(即__Block_byref_val_0 *val)
    2. __Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding持有指向”該實(shí)例自身“的指針
    3. 因此翼馆,通過__Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding可以訪問該結(jié)構(gòu)體實(shí)例的成員變量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變量會(huì)變成__Block_byref_val_0結(jié)構(gòu)體類型的自動(dòng)變量(即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例)割以。
    2. 該自動(dòng)變量被初始化為10,這個(gè)值也出現(xiàn)在結(jié)構(gòu)體實(shí)例的初始化中应媚,意味著該結(jié)構(gòu)體持有相當(dāng)于原自動(dòng)變量的成員變量严沥。
    */

    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里面增加了一個(gè)成員變量,它是一個(gè)結(jié)構(gòu)體指針中姜,指向了__Block_byref_val_0結(jié)構(gòu)體的一個(gè)實(shí)例消玄。

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


注意2:__Block_byref_val_0結(jié)構(gòu)體這個(gè)結(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; //“持有相當(dāng)于原自動(dòng)變量的成員變量”的“__main_block_impl_0結(jié)構(gòu)體實(shí)例”被追加到成員變量中

    // 構(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兩個(gè)成員變量需要特別注意:
  1. val:保存了最初的val變量,也就是說原來單純的int類型的val變量被__block修飾后生成了一個(gè)結(jié)構(gòu)體携龟。這個(gè)結(jié)構(gòu)體其中一個(gè)成員變量持有原來的val變量兔跌。
  2. __forwarding:通過__forwarding,可以實(shí)現(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;  // 相當(dāng)于原自動(dòng)變量的成員變量
};
__forwarding成員變量的實(shí)現(xiàn) :
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 最初,block變量在棧上時(shí)层亿,它的成員變量forwarding指向棧上的__block變量結(jié)構(gòu)體實(shí)例桦卒。

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

2.3.4 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共有三種類型。

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

注意:在 ARC 開啟的情況下痛单,將只會(huì)有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block嘿棘。

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

全局Block:_NSConcreteGlobalBlock

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

  • 記述全局變量的地方有Block語法時(shí)??:
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,說明它是一個(gè)全局Block旭绒。

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

??:

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 改寫的具體實(shí)現(xiàn)方式和 LLVM 不太一樣鸟妙,并且這里沒有開啟 ARC。所以這里我們看到 isa 指向的還是_NSConcreteStackBlock挥吵。但在 LLVM 的實(shí)現(xiàn)中重父,開啟 ARC 時(shí),block 應(yīng)該是 _NSConcreteGlobalBlock 類型

棧Block:_NSConcreteStackBlock

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

堆Block:_NSConcreteMallocBlock

但是,如果Block變量和__block變量復(fù)制到了堆上以后缘眶,則不再會(huì)受到變量作用域結(jié)束的影響了,因?yàn)樗兂闪硕袯lock髓废。


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

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

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

該源代碼中的函數(shù)會(huì)返回配置在棧上的Block。即當(dāng)程序執(zhí)行從該函數(shù)返回函數(shù)調(diào)用方時(shí)冈爹,變量作用域結(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有效時(shí)频伤,blk_t tmp 相當(dāng)于blk_t __strong tmp.
   * 將通過Block語法生成的Block(即配置在棧上的Block結(jié)構(gòu)體實(shí)例)
   * 賦值給相當(dāng)于Block類型的變量tmp
   */

  tmp = objc_retainBlock(tmp);

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

  return objc_autoreleaseReturnValue(tmp);

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

2.3.5 __block變量存儲(chǔ)域

  • 任何一個(gè)block被復(fù)制到堆上時(shí)鸵膏,__block變量也會(huì)一并從棧復(fù)制到堆上,并被該Block持有怎炊。
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 如果接著有其他Block被復(fù)制到堆上的話谭企,被復(fù)制的Block會(huì)持有__block變量,并增加block的引用計(jì)數(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é)束的同時(shí)攀操,附有__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類對象并被截獲的自動(dòng)變量array速和,是附有__strong修飾符的成員變量歹垫。在Objective-C中,C語言結(jié)構(gòu)體不能含有附有__strong修飾符的變量颠放。因?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)體即時(shí)含有附有__stong修飾符或__weak修飾符的變量,也可以恰當(dāng)?shù)剡M(jìn)行初始化和廢棄欲低。

在實(shí)現(xiàn)上是通過__main_block_copy_0函數(shù)和__main_block_dispose_0函數(shù)進(jìn)行的:
// 靜態(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ù)制到堆時(shí)以及堆上的Block被廢棄時(shí)會(huì)調(diào)用這些函數(shù)。
調(diào)用copy函數(shù)和dispose函數(shù)的時(shí)機(jī)
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

2.3.7 __block變量和對象

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

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

等同于:

 // ARC有效時(shí)聚假,id類型以及對象類型變量默認(rèn)附加__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ū)ο箢愋妥詣?dòng)變量”的情況下:
  • 當(dāng)Block從棧復(fù)制到堆時(shí)闰非,使用_Block_object_copy函數(shù)膘格,持有Block截獲的對象。

  • 當(dāng)堆上的Block被廢棄時(shí)财松,使用_Block_object_dispose函數(shù)瘪贱,釋放Block截獲的對象。

  • 在__block變量為“附有 __strong修飾符的id類型或?qū)ο箢愋妥詣?dòng)變量”的情形下會(huì)發(fā)生同樣的過程游岳。
  • 當(dāng)__block變量從棧復(fù)制到堆時(shí)政敢,使用_Block_object_copy函數(shù),持有賦值給__block變量的對象胚迫。

  • 當(dāng)堆上的__block變量被廢棄時(shí)喷户,使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對象访锻。

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

2.3.8 Block循環(huán)引用

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

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

如果這個(gè)對象還同時(shí)持有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也同時(shí)持有作為成員變量的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_實(shí)際上截獲了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實(shí)例方法(即不執(zhí)行賦值給成員變量blk_的Block),便會(huì)循環(huán)引用并引起內(nèi)存泄露木蹬。


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

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


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

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

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


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

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

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

缺點(diǎ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)引用時(shí),在ARC有效時(shí)饺汹,使用__weak修飾符來避免Block中的循環(huán)引用蛔添。ARC無效時(shí),__block說明符被用來避免Block中的循環(huán)引用兜辞。

總結(jié)

Blocks是“帶有自動(dòng)變量(即局部變量)的匿名函數(shù)”迎瞧。實(shí)質(zhì)是Objective-C對閉包的對象實(shí)現(xiàn),簡單說來逸吵,Block就是對象凶硅。
在Blocks中,Block常量表達(dá)式會(huì)截獲所使用的自動(dòng)變量的值(即保存該自動(dòng)變量的瞬間值)扫皱。附有 __block說明符的自動(dòng)變量可在Block中賦值足绅。
棧上的Block會(huì)復(fù)制到堆時(shí),Block上的__block變量跟著復(fù)制到堆上韩脑。只要__block變量在堆上繼續(xù)存在氢妈,那么對象就會(huì)繼續(xù)處于被持有的狀態(tài)。
ARC有效時(shí)段多,大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實(shí)現(xiàn):
  • block作為函數(shù)值返回的時(shí)候
  • 部分情況下向方法或函數(shù)中傳遞block的時(shí)候:
  • Cocoa框架的方法而且方法名中含有usingBlock等時(shí)首量。
  • Grand Central Dispatch 的API。
若由于Block引發(fā)了循環(huán)引用時(shí),在ARC有效時(shí)加缘,使用__weak修飾符來避免Block中的循環(huán)引用鸭叙。ARC無效時(shí),__block說明符被用來避免Block中的循環(huán)引用生百。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末递雀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚀浆,更是在濱河造成了極大的恐慌缀程,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件市俊,死亡現(xiàn)場離奇詭異杨凑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)摆昧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門撩满,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绅你,你說我怎么就攤上這事伺帘。” “怎么了忌锯?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵伪嫁,是天一觀的道長。 經(jīng)常有香客問我偶垮,道長张咳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任似舵,我火速辦了婚禮脚猾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砚哗。我一直安慰自己龙助,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布频祝。 她就那樣靜靜地躺著泌参,像睡著了一般。 火紅的嫁衣襯著肌膚如雪常空。 梳的紋絲不亂的頭發(fā)上沽一,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音漓糙,去河邊找鬼铣缠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蝗蛙。 我是一名探鬼主播蝇庭,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捡硅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后社搅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暮的,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年蜡豹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了齐遵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片留美。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扣典,靈堂內(nèi)的尸體忽然破棺而出妆毕,到底是詐尸還是另有隱情,我是刑警寧澤贮尖,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布笛粘,位于F島的核電站,受9級特大地震影響湿硝,放射性物質(zhì)發(fā)生泄漏薪前。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一关斜、第九天 我趴在偏房一處隱蔽的房頂上張望示括。 院中可真熱鬧,春花似錦痢畜、人聲如沸例诀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽繁涂。三九已至拱她,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扔罪,已是汗流浹背秉沼。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矿酵,地道東北人唬复。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像全肮,于是被迫代替她去往敵國和親敞咧。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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