《Objective-C 高級(jí)編程》第二章Blocks讀書(shū)筆記

前言

需要先知道的

Objective-C 轉(zhuǎn) C++的方法

因?yàn)樾枰碆lock操作的C++源碼,所以需要知道轉(zhuǎn)換的方法,自己轉(zhuǎn)過(guò)來(lái)看一看:
1. 在OC源文件block.m寫(xiě)好代碼。
2. 打開(kāi)終端,cd到block.m所在文件夾。
3. 輸入clang -rewrite-objc block.m,就會(huì)在當(dāng)前文件夾內(nèi)自動(dòng)生成對(duì)應(yīng)的block.cpp文件。

關(guān)于幾種變量的特點(diǎn)

c語(yǔ)言的函數(shù)中可能使用的變量:

  • 函數(shù)的參數(shù)
  • 自動(dòng)變量(局部變量)
  • 靜態(tài)變量(靜態(tài)局部變量)
  • 靜態(tài)全局變量
  • 全局變量

而且,由于存儲(chǔ)區(qū)域特殊,這其中有三種變量是可以在任何時(shí)候以任何狀態(tài)調(diào)用的:

  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量

而其他兩種,則是有各自相應(yīng)的作用域,超過(guò)作用域后,會(huì)被銷(xiāo)毀。

2.1 Blocks概要

2.2.1 什么是Blocks

  • Blocks是C語(yǔ)言的擴(kuò)充功能——“帶有自動(dòng)變量(即局部變量)的匿名函數(shù)”。
  • Blocks提供了類(lèi)似由C++和oc類(lèi)生成實(shí)例或?qū)ο髞?lái)保持變量值得方法,其代碼量與編寫(xiě)C語(yǔ)言函數(shù)差不多;
  • 使用Blocks可以不聲明C++和oc類(lèi),也沒(méi)有使用靜態(tài)變量、靜態(tài)全局變量或全局變量時(shí)的問(wèn)題,僅用編寫(xiě)C語(yǔ)言函數(shù)的源代碼量即可使用帶有自動(dòng)變量值的匿名函數(shù)。

2.2 Blocks模式

2.2.1 Block 語(yǔ)法

  • 與一般的C語(yǔ)言函數(shù)定義相比,完整形式的Block語(yǔ)法有兩點(diǎn)不同:

    • 沒(méi)有函數(shù)名稱(chēng)(因?yàn)槭悄涿瘮?shù))
    • 帶有“^”(便于查找)
  • Block表達(dá)式完整語(yǔ)法


    圖2-1 Block 語(yǔ)法.png
  • 省略返回值類(lèi)型的Block語(yǔ)法


    圖2-2 Block語(yǔ)法省略返回值類(lèi)型.png

*省略返回值類(lèi)型時(shí),如果表達(dá)式中有return語(yǔ)句,就使用該返回值的類(lèi)型,如果表達(dá)式中沒(méi)有return語(yǔ)句,就使用void類(lèi)型。表達(dá)式中含有多個(gè)return語(yǔ)句時(shí),所以return語(yǔ)句的返回值類(lèi)型必須相同

  • 省略返回值類(lèi)型和參數(shù)列表的Block語(yǔ)法


    圖2-3 Block語(yǔ)法省略返回值類(lèi)型和參數(shù)列表.png

2.2.2 Block類(lèi)型變量(即Block變量)

在Block語(yǔ)法下,可將Block語(yǔ)法賦值給聲明為Block類(lèi)型的變量中(即源代碼中一旦使用Block語(yǔ)法就相當(dāng)于生成了可賦值給Block類(lèi)型變量的“值”)?!癇lock”即指源代碼中的Block語(yǔ)法,也指由Block語(yǔ)法所生成的值。

  • 使用Block語(yǔ)法將Block賦值為Block類(lèi)型變量。
int(^blk)(int) = ^(int count){return count + 1;};
 //等號(hào)左側(cè)的代碼表示了這個(gè)Block的類(lèi)型:它接受一個(gè)int參數(shù),返回一個(gè)int值。
//等號(hào)右側(cè)的代碼是這個(gè)Block的值:它是等號(hào)左側(cè)定義的block類(lèi)型的一種實(shí)現(xiàn)。   
  • 由Block類(lèi)型變量向Block類(lèi)型變量賦值。
int (^blk1)(int) = blk;
    int (^blk2)(int);
    blk2 = blk1;
  • 在函數(shù)參數(shù)中使用Block類(lèi)型變量可以向函數(shù)傳遞Block。
void func(int (^blk)(int))
    {
        
    }
  • 在函數(shù)返回值中指定Block類(lèi)型,可以將Block作為函數(shù)的返回值返回。
int (^func()(int))
    {
        return ^(int count) {return count + 1;};
    }
  • 在函數(shù)參數(shù)和返回值中使用Block類(lèi)型變量時(shí),可以通過(guò)typedef為Block類(lèi)型提供別名,從而起到簡(jiǎn)化塊類(lèi)型變量名的作用。
tupedef int (^blk_t) (int);
  • 通過(guò)Block類(lèi)型變量調(diào)用Block與C語(yǔ)言通常的函數(shù)調(diào)用沒(méi)有區(qū)別(例如,在函數(shù)和方法中可以將Block類(lèi)型變量作為參數(shù))。
- (int) methodUsingBlock:(blk_t )blk rate:(int )rate
    {
        return blk (rate);
    }
  • Block類(lèi)型變量可完全像通常的C語(yǔ)言變量一樣使用(例如,可以使用指向Block類(lèi)型變量的指針,即Block的指針類(lèi)型變量)。
typedef int (^blk_t) (int);
    blk_t blk = ^(int count ) {return count + 1;};
    blk_t * blkptr = &blk;
    (*blkptr)(10);

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

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

int main()
    {
        int dmy = 256;
        int val = 10;
        const char * fmt = "val = %d\n";
        void (^blk )(void ) = ^{printf(fmt ,val );};
        val = 2;
        fmt = "These valuse were changed. val = %d\n";
        blk();
        return 0;
    }
    
    輸出結(jié)果:
    val = 10;
    

分析:Blocks中,Block表達(dá)式截獲所使用的自動(dòng)變量的值,即保存該自動(dòng)變量的瞬間值,因?yàn)閎lock表達(dá)式保存了自動(dòng)變量的值,所以在執(zhí)行block語(yǔ)法后,即使改寫(xiě)block中使用的自動(dòng)變量的值也不會(huì)影響B(tài)lock執(zhí)行時(shí)自動(dòng)變量的值。

2.2.4 __block說(shuō)明符(即存儲(chǔ)類(lèi)型修改符)

使用附有__block說(shuō)明符的自動(dòng)變量可在Block中賦值,該變量稱(chēng)為_(kāi)_block變量。

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

  • 如果將值賦值給Block中截獲的自動(dòng)變量,就會(huì)產(chǎn)生編譯錯(cuò)誤。這種情況下,需要給截獲的自動(dòng)變量附加__block說(shuō)明符。
  • 截獲Objective-C對(duì)象,調(diào)用變更該對(duì)象的方法并不會(huì)產(chǎn)生編譯錯(cuò)誤,但是,向截獲的自動(dòng)變量(即所截獲的Objective-C對(duì)象)賦值則會(huì)產(chǎn)生錯(cuò)誤。總之,賦值給截獲的自動(dòng)變量會(huì)產(chǎn)生編譯錯(cuò)誤,但使用截獲的值卻不會(huì)有任何問(wèn)題。
  • 在現(xiàn)在的Block中,截獲自動(dòng)變量的方法并沒(méi)有實(shí)現(xiàn)對(duì)C語(yǔ)言數(shù)組的截獲,但是,使用指針可以解決該問(wèn)題。
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ì)

  • 總結(jié):表層實(shí)質(zhì)是一種類(lèi)型,深層實(shí)質(zhì)是一種oc對(duì)象;
  • 通過(guò)支持Block的編譯器,含有Block語(yǔ)法的源代碼轉(zhuǎn)換為一般C語(yǔ)言編譯器能夠處理的源代碼,并作為極為普通的C語(yǔ)言源代碼被編譯。
  • 這不過(guò)是概念上的問(wèn)題,在實(shí)際編譯時(shí)無(wú)法轉(zhuǎn)換成我們能夠理解的源代碼,Clang(LLVM編譯器)具有將含有Block語(yǔ)法的源代碼轉(zhuǎn)換為我們可讀源代碼的功能。通過(guò)“-rewrite-objc”選項(xiàng)就能將含有Block語(yǔ)法的源代碼變換為C++的源代碼(本質(zhì)是使用了struct結(jié)構(gòu)的C語(yǔ)言源代碼)。
int main()
{
  void (^blk)(void) = ^{printf("Block\n");};
  blk();
  return 0;
}

通過(guò)clang轉(zhuǎn)換為以下形式:

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;      // 標(biāo)志
    int Reserved;   // 今后版本升級(jí)所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
};


// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc;
    
    // 該結(jié)構(gòu)體的構(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;
    }
};


// 最初的源代碼中的Block語(yǔ)法經(jīng)clang變換,被處理成簡(jiǎn)單的C語(yǔ)言函數(shù)(該函數(shù)以Block語(yǔ)法所屬的函數(shù)名——main和該Block語(yǔ)法在該函數(shù)出現(xiàn)的順序值——0來(lái)命名)。
// __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;     // 今后版本升級(jí)所需的區(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ù),從這里開(kāi)始閱讀源代碼
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);
    /*
    去掉轉(zhuǎn)換部分,如下:
    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;
    這段代碼對(duì)應(yīng):
    void (^blk)(void) = ^{printf("Block\n");};
    理解:
    1. 將__main_block_impl_0結(jié)構(gòu)體類(lèi)型的自動(dòng)變量(即棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例的指針)賦值給__main_block_impl_0結(jié)構(gòu)體指針類(lèi)型的變量blk
    2. __main_block_func_0是由Block語(yǔ)法轉(zhuǎn)換的C語(yǔ)言函數(shù)指針。
    3. __main_block_desc_0_DATA作為靜態(tài)全局變量初始化的__main_block_desc_0結(jié)構(gòu)體實(shí)例指針
    4. 將__main_block_impl_0的__block_impl進(jìn)行展開(kāi),__main_block_impl_0結(jié)構(gòu)體根據(jù)構(gòu)造函數(shù)會(huì)像下面進(jìn)行初始化:
    isa = &_NSConcreteStackBlock; 
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    */ 
    
    
    ((void (*)(struct __block_impl *))(
        (struct __block_impl *)blk)->FuncPtr) ((struct __block_impl *)blk);
    /*
    去掉轉(zhuǎn)換部分,如下:
    (*blk->impl.FuncPtr)(blk);
    這段代碼對(duì)應(yīng):
    blk();
    理解:
    1. 使用函數(shù)指針調(diào)用函數(shù)
    2. 由Block語(yǔ)法轉(zhuǎn)換的__main_block_func_0函數(shù)的指針被賦值成員變量FuncPtr中
    3. __main_block_func_0函數(shù)的參數(shù)__cself指向Block值,在調(diào)用該函數(shù)的源代碼中可以看出Block正是作為參數(shù)進(jìn)行了傳遞
    */
    
    return 0;
}

OC類(lèi)和對(duì)象的實(shí)質(zhì)(Block就是oc對(duì)象)

  • 在弄清楚Block就是Objective-C對(duì)象前,要先理解objc_object結(jié)構(gòu)體和objc_class結(jié)構(gòu)體。
  • id類(lèi)型是objc_object結(jié)構(gòu)體的指針類(lèi)型。
typedef struct objc_object {
        Class isa;
 } *id;
  • Class是objc_class結(jié)構(gòu)體的指針類(lèi)型。
typedef struct objc_class *Class;
  struct objc_class {
         Class isa;
  } ;
  • objc_object結(jié)構(gòu)體和objc_class結(jié)構(gòu)體歸根到底是各個(gè)對(duì)象和類(lèi)的實(shí)現(xiàn)中最基本的結(jié)構(gòu)體。
  • 如下,通過(guò)一個(gè)簡(jiǎn)單的MyObject類(lèi)來(lái)說(shuō)明Objective-C類(lèi)與對(duì)象的實(shí)質(zhì):
@interface MyObject : NSObject
{
  int val0;
  int val1;
}
  • 基于objc_object結(jié)構(gòu)體,該類(lèi)的對(duì)象的結(jié)構(gòu)體如下:
struct MyObject {
  Class isa; // 成員變量isa持有該類(lèi)的結(jié)構(gòu)體實(shí)例指針
  int val0;  // 原先MyObject類(lèi)的實(shí)例變量val0和val1被直接聲明為成員變量
  int val1;
}

理解:

  • MyObject類(lèi)的實(shí)例變量val0和val1被直接聲明為對(duì)象的成員變量。
  • “Objective-C中由類(lèi)生成對(duì)象”意味著,像該結(jié)構(gòu)體這樣“生成由該類(lèi)生成的對(duì)象的結(jié)構(gòu)體實(shí)例”。
  • 生成的各個(gè)對(duì)象(即由該類(lèi)生成的對(duì)象的各個(gè)結(jié)構(gòu)體實(shí)例),通過(guò)成員變量isa保持該類(lèi)的結(jié)構(gòu)體實(shí)例指針。
圖2-4oc類(lèi)與對(duì)象的實(shí)質(zhì).png

各類(lè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è)類(lèi)的class_t結(jié)構(gòu)體實(shí)例。
    • 該實(shí)例持有聲明的成員變量、方法的名稱(chēng)、方法的實(shí)現(xiàn)(即函數(shù)指針)、屬性以及父類(lèi)的指針,并被Objective-C運(yùn)行時(shí)庫(kù)所使用。
  • 回到正題——“Block就是Objective-C對(duì)象”,*** 先看Block結(jié)構(gòu)體:***
struct __main_block_impl_0 {
    void *isa;
    int Flags;      // 標(biāo)志
    int Reserved;   // 今后版本升級(jí)所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
    struct __main_block_desc_0* Desc;
};

理解:

  • 此__main_block_impl_0結(jié)構(gòu)體相當(dāng)于基于objc_object結(jié)構(gòu)體的Objective-C類(lèi)的對(duì)象的結(jié)構(gòu)體。
  • 對(duì)其中的isa進(jìn)行初始化,如
isa = &_NSConcreteStackBlock;

即_NSConcreteStackBlock相當(dāng)于class_t結(jié)構(gòu)體實(shí)例

  • 在將Block作為Objective-C的對(duì)象處理時(shí),關(guān)于該類(lèi)的信息放置于_NSConcreteStackBlock中。

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

截獲自動(dòng)變量值的源代碼經(jīng)clang轉(zhuǎn)換,如下:

// 結(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;
    const char *fmt; // Block語(yǔ)法表達(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語(yǔ)法表達(dá)式執(zhí)行之前就被聲明定義,所以,在Objective-C的源代碼中,執(zhí)行Block語(yǔ)法表達(dá)式時(shí)無(wú)需改動(dòng)便可使用截獲的自動(dòng)變量值。
    2. "截獲自動(dòng)變量值"意味著在執(zhí)行Block語(yǔ)法時(shí),Block語(yǔ)法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中。
    3. Block不能直接使用“C語(yǔ)言數(shù)組類(lèi)型的自動(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)
};

// 主函數(shù),從這里開(kāi)始閱讀源代碼
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);
    /*
    理解:
    1. 在初始化結(jié)構(gòu)體實(shí)例時(shí),會(huì)根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對(duì)由自動(dòng)變量追加的成員變量進(jìn)行初始化(即執(zhí)行Block語(yǔ)法使用的自動(dòng)變量fmt和val會(huì)初始化結(jié)構(gòu)體實(shí)例)
    2. __main_block_impl_0結(jié)構(gòu)體實(shí)例的初始化如下:
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
      fmt = "val = %d\n";
      val = 10;
    3. 由上可知,在__main_block_impl_0結(jié)構(gòu)體實(shí)例(即Block)中,自動(dòng)變量被截獲。
    */
    
    return 0;
}

2.3.3 __block說(shuō)明符

截獲自動(dòng)變量值的源代碼經(jīng)clang轉(zhuǎn)換,如下:

// 結(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;
    const char *fmt; // Block語(yǔ)法表達(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語(yǔ)法表達(dá)式執(zhí)行之前就被聲明定義,所以,在Objective-C的源代碼中,執(zhí)行Block語(yǔ)法表達(dá)式時(shí)無(wú)需改動(dòng)便可使用截獲的自動(dòng)變量值。
    2. "截獲自動(dòng)變量值"意味著在執(zhí)行Block語(yǔ)法時(shí),Block語(yǔ)法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中。
    3. Block不能直接使用“C語(yǔ)言數(shù)組類(lèi)型的自動(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)
};

// 主函數(shù),從這里開(kāi)始閱讀源代碼
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);
    /*
    理解:
    1. 在初始化結(jié)構(gòu)體實(shí)例時(shí),會(huì)根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對(duì)由自動(dòng)變量追加的成員變量進(jìn)行初始化(即執(zhí)行Block語(yǔ)法使用的自動(dòng)變量fmt和val會(huì)初始化結(jié)構(gòu)體實(shí)例)
    2. __main_block_impl_0結(jié)構(gòu)體實(shí)例的初始化如下:
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
      fmt = "val = %d\n";
      val = 10;
    3. 由上可知,在__main_block_impl_0結(jié)構(gòu)體實(shí)例(即Block)中,自動(dòng)變量被截獲。
    */
    
    return 0;
}

2.3.4 Block存儲(chǔ)域

從“__block說(shuō)明符”一節(jié)中可知,*** Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,___block變量轉(zhuǎn)換為_(kāi)_block的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量。所謂結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例。 ***

表 Block與__block變量的實(shí)質(zhì)

名稱(chēng) 實(shí)質(zhì)
Block 棧上block的結(jié)構(gòu)體實(shí)例
_block變量 棧上_block變量的結(jié)構(gòu)體實(shí)例
  • 從“Block的實(shí)質(zhì)”一節(jié)中可知,Block是Objective-C對(duì)象,并且該Block的類(lèi)為_(kāi)NSConcreteStackBlock。此外,與之類(lèi)似的還有兩個(gè)類(lèi):

  • _NSConcreteStackBlock —— *** 該類(lèi)對(duì)象設(shè)置在棧上 ***

  • _NSConcreteGlobalBlock —— *** 該類(lèi)對(duì)象設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中 ***

  • _NSConcreteMallocBlock —— *** 該類(lèi)對(duì)象設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(即堆中) ***

在內(nèi)存中的位置如下圖:


圖2-6 設(shè)置為Block的存儲(chǔ)域.png

注意

  • 由于_NSConcreteGlobalBlock類(lèi)生成的Block對(duì)象設(shè)置在程序的數(shù)據(jù)區(qū)域中(該類(lèi)會(huì)運(yùn)用在“全局變量”的聲明中),由此該類(lèi)的結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴(lài)于執(zhí)行時(shí)的狀態(tài),所以整個(gè)程序只需一個(gè)實(shí)例。
  • 只在截獲自動(dòng)變量時(shí),Block的結(jié)構(gòu)體實(shí)例截獲的值才會(huì)根據(jù)執(zhí)行時(shí)的狀態(tài)變化,而在不截獲自動(dòng)變量時(shí),Block的結(jié)構(gòu)體實(shí)例每次截獲的值都相同。也就是說(shuō),即時(shí)在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語(yǔ)法時(shí),只要Block不截獲自動(dòng)變量,就可以將Block的結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域。

總結(jié)

  • Block為_(kāi)NSConcreteGlobalBlock類(lèi)對(duì)象(即Block配置在程序的數(shù)據(jù)區(qū)域中)的情況有兩種:

    • 記述全局變量的地方有Block語(yǔ)法時(shí)
    • Block語(yǔ)法表達(dá)式中不使用“應(yīng)截獲的自動(dòng)變量”時(shí)
  • 除此之外的Block語(yǔ)法生成的Block為_(kāi)NSConcreteStackBlock類(lèi)對(duì)象(即類(lèi)對(duì)象設(shè)置在棧上)。

  • 而,_NSConcreteMallocBlock類(lèi)是Block超出變量作用域可存在的原因。

遺留問(wèn)題

“__block說(shuō)明符”一節(jié)中遺留的問(wèn)題:

  • Block超出變量作用域可存在的原因
  • __block變量的結(jié)構(gòu)體成員變量__forwarding存在的原因

配置在全局變量的Block,從變量作用域外也可以通過(guò)指針安全的使用,而配置在棧上的Block,如果其所屬的變量作用域結(jié)束,該Block就被廢棄。如圖:

圖2-7棧上的Block與__block變量.png

為此,Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法來(lái)解決這個(gè)問(wèn)題。

如圖:

圖2-8 從棧復(fù)制到堆上的Block與__block變量.png

實(shí)現(xiàn)機(jī)制:

  • 復(fù)制到堆上的Block將_NSConcreteMallocBlock類(lèi)對(duì)象寫(xiě)入Block的結(jié)構(gòu)體實(shí)例的成員變量isa。
impl.isa = &_NSConcreteMallocBlock;

而__block變量的結(jié)構(gòu)體成員變量forwarding可以實(shí)現(xiàn)無(wú)論變量配置在棧上還是堆上時(shí)都能夠正確地訪問(wèn)__block變量。

ARC有效時(shí),大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實(shí)現(xiàn)

實(shí)際上,ARC有效時(shí),大多數(shù)編譯器會(huì)恰當(dāng)?shù)剡M(jìn)行判斷,自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼。

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也被廢棄。雖然有這樣的問(wèn)題,但該源代碼通過(guò)對(duì)應(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);
  
  tmp = objc_retainBlock(tmp);
  
  return objc_autoreleaseReturnValue(tmp);
}


理解:

  • ARC有效時(shí),blk_t tmp 相當(dāng)于blk_t __strong tmp.
  • objc_retainBlock實(shí)際上是Block_copy函數(shù)。
    詳細(xì)的注釋?zhuān)?/li>
blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
  
  /*
   * 將通過(guò)Block語(yǔ)法生成的Block(即配置在棧上的Block結(jié)構(gòu)體實(shí)例)
   * 賦值給相當(dāng)于Block類(lèi)型的變量tmp
   */
  
  tmp = _Block_copy(tmp);
  
  /*
   * _Block_copy函數(shù)
   * 將棧上的Block復(fù)制到堆上
   * 復(fù)制后,將堆上的地址作為指針賦值給變量tmp
   */
  
  return objc_autoreleaseReturnValue(tmp);
  
  /*
   * 將堆上的Block作為Objective-C對(duì)象
   * 注冊(cè)到autoreleasepool中,然后返回該對(duì)象
   */
}

*** 將Block作為函數(shù)返回值返回時(shí),編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。 ***

在少數(shù)情況下,Block從棧上復(fù)制到堆上的代碼的手動(dòng)實(shí)現(xiàn)

如果*** 向方法或函數(shù)的參數(shù)中傳遞Block時(shí) ***,編譯器將不能進(jìn)行判斷,需要使用“copy實(shí)例方法”手動(dòng)復(fù)制。
但是,以下方法或函數(shù)不需要手動(dòng)復(fù)制:

  • Cocoa框架的方法且方法名中含有usingBlock等時(shí)
  • Grand Central Dispathc 的API

*** 對(duì)Block語(yǔ)法調(diào)用copy方法 ***

- (id)getBlockArray
{
  int val = 10;
  return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk:%d", val);},
                                           ^{NSLog(@"blk:%d", val);}, nil];
}

id obj = getBlockArray();

typedef void (^blk_t)(void);

blk_t blk = (blk_t)[obj objectAtIndex:0];

blk();

該源代碼的blk(),即Block在執(zhí)行時(shí)發(fā)生異常,應(yīng)用程序強(qiáng)制結(jié)束。這是由于*** getBlockArray函數(shù)執(zhí)行結(jié)束時(shí),棧上的Block被廢棄的緣故。 *** 此時(shí),編譯器不能判斷是否需要復(fù)制。也可以不讓編譯器進(jìn)行判斷,而使其在所有情況下都能復(fù)制。但將Block從棧上復(fù)制到堆上是相當(dāng)消耗CPU的。當(dāng)Block設(shè)置在棧上也能夠使用時(shí),將Block從棧上復(fù)制到堆上只是在浪費(fèi)CPU資源。因此只在此情形下讓編程人員手動(dòng)進(jìn)行復(fù)制。

對(duì)源代碼修改一下,便可正常運(yùn)行:

- (id)getBlockArray
{
  int val = 10;
  return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk:%d", val);} copy],
                                           [^{NSLog(@"blk:%d", val);} copy], nil];
}

id obj = getBlockArray();

typedef void (^blk_t)(void);

blk_t blk = (blk_t)[obj objectAtIndex:0];

blk();

*** 對(duì)Block類(lèi)型變量調(diào)用copy方法 ***

按配置Block的存儲(chǔ)域,使用copy方法產(chǎn)生的復(fù)制效果

表 Block的副本

Block的類(lèi) 副本源的配置存儲(chǔ)域 復(fù)制效果
_NSConcreteStackBlock 從棧復(fù)制到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么也不做
_NSConcreteMallocBlock 引用計(jì)數(shù)增加

不管Block配置在何處,用copy方法復(fù)制都不會(huì)引起任何問(wèn)題。在不確定時(shí)調(diào)用copy方法即可。

在ARC有效時(shí),多次調(diào)用copy方法完全沒(méi)有問(wèn)題

blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過(guò)多次復(fù)制,變量blk仍然持有Block的強(qiáng)引用,該Block不會(huì)被廢棄。

2.3.5 __block變量存儲(chǔ)域

從“Block存儲(chǔ)域”一節(jié)可知,*** 使用__block變量的Block從棧復(fù)制到堆上時(shí),__block變量也會(huì)受到影響。 ***

表 Block從棧復(fù)制到堆時(shí)對(duì)__block變量產(chǎn)生的影響

__block變量的配置存儲(chǔ)域 Block從棧復(fù)制到堆時(shí)的影響
從棧復(fù)制到堆并被Block持
被Block持有

*** 在一個(gè)Block中使用__block變量 ***

圖2-9 在一個(gè)Block中使用__block變量.png

*** 在多個(gè)Block中使用__block變量 ***

圖2-10 在多個(gè)Block中使用__block變量.png

*** Block的廢棄和__block變量的釋放 ***

圖2-11 Block的廢棄和__block變量的釋放.png

遺留的問(wèn)題

“Block存儲(chǔ)域”一節(jié)中遺留的問(wèn)題:

  • 使用__block變量的結(jié)構(gòu)體成員變量__forwarding的原因

不管__block變量配置在棧上還是在堆上,都能夠正確地訪問(wèn)該變量

正如這句話所訴,通過(guò)Block的復(fù)制,__block變量也會(huì)從棧復(fù)制到堆上。此時(shí)可同時(shí)訪問(wèn)棧上的__block變量和堆上的__block變量。

__block int val = 0;  // __block變量

void (^blk)(void) = [^{++val;} copy]; // Block

++val;

blk();

NSLog(@"%d", val);

利用copy方法復(fù)制使用了__block變量的Block語(yǔ)法。此時(shí),Block和__block變量均從棧復(fù)制到堆。

*** 在Block語(yǔ)法表達(dá)式中,使用初始化后的__block變量 ***

^{++val;}

*** 在Block語(yǔ)法表達(dá)式之后,使用與Block無(wú)關(guān)的__block變量 ***

++val;

然而,以上兩種源代碼都可以轉(zhuǎn)換為:

++(val.__forwaring->val);

在變化Block語(yǔ)法的函數(shù)中,該變量val為 *** 復(fù)制到堆上的__block變量的結(jié)構(gòu)體實(shí)例 ,而使用與Block無(wú)關(guān)的變量val,為 復(fù)制前棧上的__block變量的結(jié)構(gòu)體實(shí)例 ***。

但是,棧上的__block變量的結(jié)構(gòu)體實(shí)例(即變量val)在__block變量從棧復(fù)制到堆上時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制目標(biāo)堆上的__block變量的結(jié)構(gòu)體實(shí)例的地址。

圖2-12 復(fù)制__block變量.png

2.3.6 截獲對(duì)象

{
  id array = [[NSMutableArray alloc] init];
}

該源代碼生成并持有NSMutableArray類(lèi)的對(duì)象,但是附有__strong修飾符的賦值目標(biāo)(變量array)變量作用域立即就會(huì)結(jié)束,因此對(duì)象被立即釋放并廢棄。

blk_t blk;

{
  id array = [[NSMutableArray alloc] init];
  blk = [^(id obj){
  
      [array addObject:obj];
      
      NSLog(@"array count = %ld", [array count]);
  } copy]; // 調(diào)用copy方法(Block從棧復(fù)制到堆)
}

blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);


變量作用域結(jié)束的同時(shí),變量array被廢棄,其對(duì)NSMutableArray類(lèi)的對(duì)象的強(qiáng)引用失效,因此NSMutableArray類(lèi)的對(duì)象被釋放并廢棄(此處我不確定是否會(huì)被廢棄)。但是,該源代碼運(yùn)行正常,執(zhí)行結(jié)果如下:

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

這意味著賦值給變量array的NSMutableArray類(lèi)的對(duì)象在Block的執(zhí)行部分超出其變量作用域而存在。

經(jīng)clang轉(zhuǎn)換:

/* Block的結(jié)構(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;
    id __strong array; 
    /*
    理解:
    1. 被NSMutableArray類(lèi)對(duì)象并被截獲的自動(dòng)變量array,是附有__strong修飾符的成員變量。在Objective-C中,C語(yǔ)言結(jié)構(gòu)體不能含有附有__strong修飾符的變量。因?yàn)榫幾g器不知道何時(shí)進(jìn)行C語(yǔ)言結(jié)構(gòu)體的初始化和廢棄操作,不能很好地管理內(nèi)存。
    2. 但是,Objective-C的運(yùn)行時(shí)庫(kù)能準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時(shí)機(jī),因此Block的結(jié)構(gòu)體即時(shí)含有附有__stong修飾符或__weak修飾符的變量,也可以恰當(dāng)?shù)剡M(jìn)行初始化和廢棄。
    */
    
    // 構(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;
    }
};

// 靜態(tài)函數(shù) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj)
{
    id __strong array = __cself->array;
    
    [array addObject:obj];
    
    NSLog(@"array count = %ld", [array count]);
}

// 靜態(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->array, src->array, BLOCK_FIELD_IS_OBJECT);
    /*
    理解:
    1. __main_block_copy_0函數(shù)使用_Block_object_assign函數(shù)將“對(duì)象類(lèi)型對(duì)象”賦值給Block的結(jié)構(gòu)體成員變量array中并持有該對(duì)象
    2. _Block_object_assign函數(shù)調(diào)用“相當(dāng)于ratain實(shí)例方法的函數(shù)”,將“對(duì)象”賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中。
    */
}

// 靜態(tài)函數(shù) __main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0 *src){
    _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
    /*
    理解:
    1. __main_block_dispose_0函數(shù)使用_Block_object_dispose函數(shù),釋放賦值在Block的結(jié)構(gòu)體成員變量array中的對(duì)象。
    2. _Block_object_dispose函數(shù)調(diào)用相當(dāng)于release實(shí)例方法的函數(shù),釋放賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中的對(duì)象。
    */
}

// 靜態(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ù)包括使用指針全都沒(méi)有被調(diào)用。
2. 而是,在Block從棧復(fù)制到堆時(shí)以及堆上的Block被廢棄時(shí)會(huì)調(diào)用這些函數(shù)。
*/

/* Block語(yǔ)法,使用Block部分 */

blk_t blk;

{
    id __strong array = [[NSMutableArray alloc] init];
    
    blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, array, 0x22000000);
    blk = [blk copy];
}

(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);

表 調(diào)用copy函數(shù)和dispose函數(shù)的時(shí)機(jī)

函數(shù) 調(diào)用時(shí)機(jī)
copy函數(shù) 棧上的Block復(fù)制到堆時(shí)
dispose函數(shù) 堆上的Block被廢棄時(shí)

*** 何時(shí)棧上的Block會(huì)復(fù)制到堆 ***

  • 調(diào)用Block的copy實(shí)例方法時(shí)
  • Block作為函數(shù)返回值返回時(shí)
  • 將Block賦值給附有__strong修飾符id類(lèi)型的類(lèi)或Block類(lèi)型成員變量時(shí)
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時(shí)

在棧上的Block被復(fù)制到堆時(shí),copy函數(shù)被調(diào)用,而在釋放復(fù)制到堆上的Block后,誰(shuí)都不持有Block而被廢棄時(shí),dispose函數(shù)被調(diào)用。正因?yàn)檫@種構(gòu)造,通過(guò)使用附有__strong修飾符的自動(dòng)變量,Block中截獲的對(duì)象才能給超出其變量作用域而存在。

*** 如何區(qū)分copy函數(shù)和dispose函數(shù)的對(duì)象類(lèi)型 ***

表 截獲對(duì)象時(shí)和使用__block變量時(shí)的不同

對(duì)象 BLOCK_FIELD_IS_OBJECT
__block變量 BLOCK_FIELD_IS_BYREF
  • 通過(guò)BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF參數(shù),區(qū)分copy函數(shù)和dispose函數(shù)的對(duì)象類(lèi)型是對(duì)象還是__block變量。

  • 但是,與copy函數(shù)持有被截獲的對(duì)象,dispose函數(shù)釋放截獲的對(duì)象相同,copy函數(shù)持有所使用的__block變量,dispose函數(shù)釋放所使用的__block。

  • 由此可知,Block中使用的賦值給附有__stong修飾符的自動(dòng)變量的對(duì)象和復(fù)制到堆上的__block變量,由于被堆上的Block所持有,因而可超出其變量作用域而存在。

*** 何種情形下,不調(diào)用Block的copy實(shí)例方法 ***
在Block使用對(duì)象類(lèi)型自動(dòng)變量是,除以下情形外,推薦調(diào)用Block的copy實(shí)例方法:

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

2.3.7 __block變量和對(duì)象

*** 附加__strong修飾符的對(duì)象類(lèi)型__block變量和自動(dòng)變量***

__block說(shuō)明符可指定“任何類(lèi)型”的自動(dòng)變量。

下面指定用于賦值Objective-C對(duì)象的id類(lèi)型自動(dòng)變量:

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

ARC有效時(shí),id類(lèi)型以及對(duì)象類(lèi)型變量默認(rèn)附加__strong修飾符。
所以,該代碼等同于:

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

經(jīng)clang轉(zhuǎn)換:

/* __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類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量”的情況下,當(dāng)Block從棧復(fù)制到堆時(shí),使用_Block_object_copy函數(shù),持有Block截獲的對(duì)象。當(dāng)堆上的Block被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放Block截獲的對(duì)象。

在__block變量為“附有__strong修飾符的id類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量”的情形下會(huì)發(fā)生同樣的過(guò)程。當(dāng)__block變量從棧復(fù)制到堆時(shí),使用_Block_object_copy函數(shù),持有賦值給__block變量的對(duì)象。當(dāng)堆上的__block變量被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對(duì)象。

由此可知,即時(shí)對(duì)象賦值給“復(fù)制到堆上的附有__strong修飾符的對(duì)象類(lèi)型__block變量”中,只要__block變量在堆上繼續(xù)存在,那么該對(duì)象就會(huì)繼續(xù)處于被持有的狀態(tài)。這與在Block中對(duì)象賦值給“附有__strong修飾符的對(duì)象類(lèi)型自動(dòng)變量”相同。

*** 附有__weak修飾符的id類(lèi)型自動(dòng)變量 ***
在Block中使用附有__weak修飾符的id類(lèi)型自動(dòng)變量:

blk_t blk;

{
    id array = [[NSMutableArray alloc] init];
    id __weak weakArray = array;
    
    blk = [^(id obj){
        
        [weakArray addObject:obj];
        
        NSLog(@"weakArray count = %ld", [weakArray count]);
    } copy];
}

blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);

執(zhí)行結(jié)果:

weakArray count = 0
weakArray count = 0
weakArray count = 0

該段代碼能夠正常運(yùn)行。這是因?yàn)樵谧兞孔饔糜蚪Y(jié)束時(shí),附有__strong修飾符的自動(dòng)變量array所持有的NSMutableArray類(lèi)對(duì)象會(huì)被釋放被廢棄,而附有__weak修飾符的自動(dòng)變量weakArray由于對(duì)NSMutableArray類(lèi)對(duì)象持有弱引用,此時(shí)nil賦值在自動(dòng)變量weakArray上。

*** 附有__weak修飾符的__block變量 ***

blk_t blk;

{
    id array = [[NSMutableArray alloc] init];
    __block id __weak blockWeakArray = array;
    
    blk = [^(id obj){
        
        [blockWeakArray addObject:obj];
        
        NSLog(@"blockWeakArray count = %ld", [blockWeakArray count]);
    } copy];
}

blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);

執(zhí)行結(jié)果:

blockWeakArray count = 0
blockWeakArray count = 0
blockWeakArray count = 0

這段代碼也能正常運(yùn)行。這是因?yàn)榧磿r(shí)附加了__block說(shuō)明符,在變量作用域結(jié)束時(shí),附有__strong修飾符的自動(dòng)變量array所持有的NSMutableArray類(lèi)對(duì)象會(huì)被釋放被廢棄,而附有__weak修飾符的自動(dòng)變量blockWeakArray由于對(duì)NSMutableArray類(lèi)對(duì)象持有弱引用,此時(shí)nil賦值在自動(dòng)變量blockWeakArray上。

注意

  • 由于附有__unsafe_unretained修飾符的變量只不過(guò)與指針相同,所以不管在Block中使用還是附加到__block變量中,也不會(huì)像__strong修飾符或__weak修飾符那樣進(jìn)行處理。在使用附有__unsafe_unretained修飾符的變量時(shí),注意不要通過(guò)懸掛指針訪問(wèn)已被廢棄的對(duì)象,否則程序可能會(huì)崩潰!
  • 沒(méi)有設(shè)定__autoreleasing修飾符與Block同時(shí)使用。
  • __autoreleasing修飾符與__block說(shuō)明符同時(shí)使用會(huì)產(chǎn)生編譯錯(cuò)誤。

2.3.8 Block循環(huán)引用

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

*** 使用Block類(lèi)型成員變量和附有__strong修飾符的self出現(xiàn)循環(huán)引用 ***

typedeft void (^blk_t)(void);

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

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

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

int main()
{
    id o = [[MyObject alloc] init];
    
    NSLog(@"%@", o);
    
    return 0;
}

編譯該源代碼時(shí),編譯器會(huì)發(fā)出警告,這是因?yàn)槌霈F(xiàn)了循環(huán)引用,從而導(dǎo)致dealloc實(shí)例方法沒(méi)有被調(diào)用。

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

*** 使用Block類(lèi)型成員變量和附有__weak修飾符的self避免循環(huán)引用 ***
為避免此循環(huán)引用,可聲明附有__weak修飾符的變量,并將self賦值給該變量,然后在Block語(yǔ)法中使用該變量。

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

*** 面向iOS4,使用__unsafe_unretained修飾符 ***

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

面向iOS4,使用__unsafe_unretained修飾符替代__weak修飾符,并且不用擔(dān)心懸掛指針問(wèn)題。

*** Block中沒(méi)有使用self,但是截獲了self ***

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

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


該源代碼,如果編譯,編譯器會(huì)發(fā)出警告(出現(xiàn)循環(huán)引用)。這是因?yàn)锽lock語(yǔ)法中使用的obj_實(shí)際上截獲了self,而對(duì)編譯器來(lái)說(shuō),obj_只不過(guò)是對(duì)象的結(jié)構(gòu)體的成員變量。

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

為避免循環(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;
}


該源代碼沒(méi)有引起循環(huán)引用。但是,如果不調(diào)用execBlock實(shí)例方法(即不執(zhí)行賦值給成員變量blk_的Block),便會(huì)循環(huán)引用并引起內(nèi)存泄露。該種循環(huán)引用可參看下面。

*** 使用__block變量不恰當(dāng)會(huì)出現(xiàn)循環(huán)引用 ***
在生成并持有MyObject類(lèi)對(duì)象的狀態(tài)下會(huì)引起以下循環(huán)引用,如下圖:

  • MyObject類(lèi)對(duì)象持有Block
  • Block持有__block變量
  • __block變量持有MyObject類(lèi)對(duì)象
圖2-14使用Block成員變量避免循環(huán)引用.png

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

如果只想execBlock實(shí)例方法,Block被執(zhí)行,nil被賦值在__block變量blockSelf中。

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

因此,__block變量blockSelf對(duì)MyObject類(lèi)對(duì)象的強(qiáng)引用失效,從而避免了循環(huán)引用,如下圖:

  • MyObject類(lèi)對(duì)象持有Block
  • Block持有__block變量
圖2-16避免循環(huán)引用.png

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

優(yōu)點(diǎn)

通過(guò)__block變量可控制對(duì)象的持有期間
在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可(不必?fù)?dān)心懸掛指針)
在執(zhí)行Block時(shí)可動(dòng)態(tài)地決定是否將nil或其他對(duì)象賦值在__block變量中,從而避免出現(xiàn)循環(huán)引用。

缺點(diǎn)

為避免循環(huán)引用必須執(zhí)行Block
存在執(zhí)行了Block語(yǔ)法,卻不執(zhí)行Block的路徑時(shí),無(wú)法避免循環(huán)引用。

總結(jié)

若由于Block引發(fā)了循環(huán)引用時(shí),根據(jù)Block的用途選擇使用__block變量、__weak修飾符或__unsafe_unretained修飾符來(lái)避免循環(huán)引用。

2.3.9 copy/release

*** ARC無(wú)效時(shí),需要用copy實(shí)例方法手動(dòng)將Blocl從棧復(fù)制到堆,用release實(shí)例方法來(lái)釋放復(fù)制的Block。 ***

void (^blk_on_heap)(void) = [blk_on_stack copy];

[blk_on_heap release];

*** 只要Block有一次復(fù)制并配置在堆上,就可通過(guò)retain實(shí)例方法持有。***

[blk_on_heap retain];

*** 但是,對(duì)于配置在棧上的Block調(diào)用retain實(shí)例方法則不起任何作用。 ***

[blk_on_stack retain];

該源代碼中,雖然對(duì)賦值給blk_on_stack的棧上的Block調(diào)用了retain實(shí)例方法,但實(shí)際上對(duì)此源代碼不起任何作用。因此,推薦使用copy實(shí)例方法來(lái)持有Block(不用retain實(shí)例方法)。

由于Block是C語(yǔ)言的擴(kuò)展,所以在C語(yǔ)言中也可以使用Block語(yǔ)法。此時(shí)使用“Block_copy函數(shù)”和“Block_release函數(shù)”代替copy/release實(shí)例方法。

void (^blk_on_heap)(void) = Block_copy(blk_on_stack);

Block_release(blk_on_heap);

*** ARC無(wú)效時(shí),__block說(shuō)明符被用來(lái)避免Block中的循環(huán)引用。***
這是由于當(dāng)Block從棧復(fù)制到堆時(shí),若Block使用的變量為附有__block說(shuō)明符的id類(lèi)型或?qū)ο箢?lèi)型的自動(dòng)變量,不會(huì)被retain;若Block使用的變量為沒(méi)有__block說(shuō)明符的id類(lèi)型或?qū)ο箢?lèi)型的自動(dòng)變量,則被retain。

typedeft void (^blk_t)(void);

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

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

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

int main()
{
    id o = [[MyObject alloc] init];
    
    NSLog(@"%@", o);
    
    return 0;
}

該源代碼無(wú)論ARC有效還是無(wú)效都會(huì)引起循環(huán)引用,Block持有self,self持有Block。

可使用__block變量來(lái)避免出現(xiàn)該問(wèn)題。

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


這時(shí),由于Block使用__block變量,所以不會(huì)被retain。

注意

ARC有效時(shí),__block說(shuō)明符和__unsafe_unretained修飾符一起使用,來(lái)解決附有__unsafe_unretained修飾符的自動(dòng)變量不能retain的問(wèn)題。

__block說(shuō)明符在ARC有效無(wú)效時(shí)的用途有很大的區(qū)別,所以,在使用__block說(shuō)明符必須清楚源代碼是在ARC有效還是無(wú)效的情況下編譯。

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

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