《Objective-C 高級編程》第二章Blocks讀書筆記

前言

需要先知道的

Objective-C 轉 C++的方法

因為需要看Block操作的C++源碼季希,所以需要知道轉換的方法耻警,自己轉過來看一看:
1. 在OC源文件block.m寫好代碼。
2. 打開終端涂屁,cd到block.m所在文件夾。
3. 輸入clang -rewrite-objc block.m灰伟,就會在當前文件夾內(nèi)自動生成對應的block.cpp文件拆又。

關于幾種變量的特點

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

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

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

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

而其他兩種帖族,則是有各自相應的作用域,超過作用域后挡爵,會被銷毀竖般。

2.1 Blocks概要

2.2.1 什么是Blocks

  • Blocks是C語言的擴充功能——“帶有自動變量(即局部變量)的匿名函數(shù)”。
  • Blocks提供了類似由C++和oc類生成實例或對象來保持變量值得方法茶鹃,其代碼量與編寫C語言函數(shù)差不多涣雕;
  • 使用Blocks可以不聲明C++和oc類浙炼,也沒有使用靜態(tài)變量仁期、靜態(tài)全局變量或全局變量時的問題,僅用編寫C語言函數(shù)的源代碼量即可使用帶有自動變量值的匿名函數(shù)。

2.2 Blocks模式

2.2.1 Block 語法

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

    • 沒有函數(shù)名稱(因為是匿名函數(shù))
    • 帶有“^”(便于查找)
  • Block表達式完整語法


    圖2-1 Block 語法.png
  • 省略返回值類型的Block語法


    圖2-2 Block語法省略返回值類型.png

*省略返回值類型時,如果表達式中有return語句榆骚,就使用該返回值的類型登淘,如果表達式中沒有return語句,就使用void類型流译。表達式中含有多個return語句時逞怨,所以return語句的返回值類型必須相同

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


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

2.2.2 Block類型變量(即Block變量)

在Block語法下,可將Block語法賦值給聲明為Block類型的變量中(即源代碼中一旦使用Block語法就相當于生成了可賦值給Block類型變量的“值”)福澡〉猓“Block”即指源代碼中的Block語法,也指由Block語法所生成的值竞漾。

  • 使用Block語法將Block賦值為Block類型變量眯搭。
int(^blk)(int) = ^(int count){return count + 1;};
 //等號左側的代碼表示了這個Block的類型:它接受一個int參數(shù),返回一個int值业岁。
//等號右側的代碼是這個Block的值:它是等號左側定義的block類型的一種實現(xiàn)鳞仙。   
  • 由Block類型變量向Block類型變量賦值。
int (^blk1)(int) = blk;
    int (^blk2)(int);
    blk2 = blk1;
  • 在函數(shù)參數(shù)中使用Block類型變量可以向函數(shù)傳遞Block笔时。
void func(int (^blk)(int))
    {
        
    }
  • 在函數(shù)返回值中指定Block類型棍好,可以將Block作為函數(shù)的返回值返回。
int (^func()(int))
    {
        return ^(int count) {return count + 1;};
    }
  • 在函數(shù)參數(shù)和返回值中使用Block類型變量時允耿,可以通過typedef為Block類型提供別名借笙,從而起到簡化塊類型變量名的作用。
tupedef int (^blk_t) (int);
  • 通過Block類型變量調用Block與C語言通常的函數(shù)調用沒有區(qū)別(例如较锡,在函數(shù)和方法中可以將Block類型變量作為參數(shù))业稼。
- (int) methodUsingBlock:(blk_t )blk rate:(int )rate
    {
        return blk (rate);
    }
  • Block類型變量可完全像通常的C語言變量一樣使用(例如,可以使用指向Block類型變量的指針蚂蕴,即Block的指針類型變量)低散。
typedef int (^blk_t) (int);
    blk_t blk = ^(int count ) {return count + 1;};
    blk_t * blkptr = &blk;
    (*blkptr)(10);

2.2.3 截獲自動變量值

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

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;
    }
    
    輸出結果:
    val = 10;
    

分析:Blocks中,Block表達式截獲所使用的自動變量的值鸟整,即保存該自動變量的瞬間值引镊,因為block表達式保存了自動變量的值,所以在執(zhí)行block語法后,即使改寫block中使用的自動變量的值也不會影響B(tài)lock執(zhí)行時自動變量的值弟头。

2.2.4 __block說明符(即存儲類型修改符)

使用附有__block說明符的自動變量可在Block中賦值吩抓,該變量稱為__block變量。

2.2.5 截獲的自動變量

  • 如果將值賦值給Block中截獲的自動變量亮瓷,就會產(chǎn)生編譯錯誤琴拧。這種情況下,需要給截獲的自動變量附加__block說明符嘱支。
  • 截獲Objective-C對象蚓胸,調用變更該對象的方法并不會產(chǎn)生編譯錯誤,但是除师,向截獲的自動變量(即所截獲的Objective-C對象)賦值則會產(chǎn)生錯誤沛膳。總之汛聚,賦值給截獲的自動變量會產(chǎn)生編譯錯誤锹安,但使用截獲的值卻不會有任何問題。
  • 在現(xiàn)在的Block中倚舀,截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲叹哭,但是,使用指針可以解決該問題痕貌。
const char *text = "hello";
void (^blk)(void) = ^{
  printf("%c\n", text[2]);
};
blk();

2.3 Blocks的實現(xiàn)

2.3.1 Block的實質

  • 總結:表層實質是一種類型风罩,深層實質是一種oc對象;
  • 通過支持Block的編譯器舵稠,含有Block語法的源代碼轉換為一般C語言編譯器能夠處理的源代碼超升,并作為極為普通的C語言源代碼被編譯。
  • 這不過是概念上的問題哺徊,在實際編譯時無法轉換成我們能夠理解的源代碼室琢,Clang(LLVM編譯器)具有將含有Block語法的源代碼轉換為我們可讀源代碼的功能。通過“-rewrite-objc”選項就能將含有Block語法的源代碼變換為C++的源代碼(本質是使用了struct結構的C語言源代碼)落追。
int main()
{
  void (^blk)(void) = ^{printf("Block\n");};
  blk();
  return 0;
}

通過clang轉換為以下形式:

// 結構體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;      // 標志
    int Reserved;   // 今后版本升級所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
};


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


// 最初的源代碼中的Block語法經(jīng)clang變換盈滴,被處理成簡單的C語言函數(shù)(該函數(shù)以Block語法所屬的函數(shù)名——main和該Block語法在該函數(shù)出現(xiàn)的順序值——0來命名)。
// __ceself為指向Block值的變量轿钠。
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}

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

// main函數(shù),從這里開始閱讀源代碼
int main()
{
    // 調用結構體__main_block_impl_0的構造函數(shù)__main_block_impl_0
    void (*blk)(void) =
        (void (*)(void)) & __main_block_impl_0(
            (void *)__main_block_func_0, &__mian_block_desc_0_DATA);
    /*
    去掉轉換部分铅乡,如下:
    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;
    這段代碼對應:
    void (^blk)(void) = ^{printf("Block\n");};
    理解:
    1. 將__main_block_impl_0結構體類型的自動變量(即棧上生成的__main_block_impl_0結構體實例的指針)賦值給__main_block_impl_0結構體指針類型的變量blk
    2. __main_block_func_0是由Block語法轉換的C語言函數(shù)指針妹卿。
    3. __main_block_desc_0_DATA作為靜態(tài)全局變量初始化的__main_block_desc_0結構體實例指針
    4. 將__main_block_impl_0的__block_impl進行展開陷寝,__main_block_impl_0結構體根據(jù)構造函數(shù)會像下面進行初始化:
    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);
    /*
    去掉轉換部分,如下:
    (*blk->impl.FuncPtr)(blk);
    這段代碼對應:
    blk();
    理解:
    1. 使用函數(shù)指針調用函數(shù)
    2. 由Block語法轉換的__main_block_func_0函數(shù)的指針被賦值成員變量FuncPtr中
    3. __main_block_func_0函數(shù)的參數(shù)__cself指向Block值,在調用該函數(shù)的源代碼中可以看出Block正是作為參數(shù)進行了傳遞
    */
    
    return 0;
}

OC類和對象的實質(Block就是oc對象)

  • 在弄清楚Block就是Objective-C對象前蹈垢,要先理解objc_object結構體和objc_class結構體岩馍。
  • id類型是objc_object結構體的指針類型驹尼。
typedef struct objc_object {
        Class isa;
 } *id;
  • Class是objc_class結構體的指針類型地啰。
typedef struct objc_class *Class;
  struct objc_class {
         Class isa;
  } ;
  • objc_object結構體和objc_class結構體歸根到底是各個對象和類的實現(xiàn)中最基本的結構體愁拭。
  • 如下,通過一個簡單的MyObject類來說明Objective-C類與對象的實質:
@interface MyObject : NSObject
{
  int val0;
  int val1;
}
  • 基于objc_object結構體,該類的對象的結構體如下:
struct MyObject {
  Class isa; // 成員變量isa持有該類的結構體實例指針
  int val0;  // 原先MyObject類的實例變量val0和val1被直接聲明為成員變量
  int val1;
}

理解:

  • MyObject類的實例變量val0和val1被直接聲明為對象的成員變量亏吝。
  • “Objective-C中由類生成對象”意味著岭埠,像該結構體這樣“生成由該類生成的對象的結構體實例”。
  • 生成的各個對象(即由該類生成的對象的各個結構體實例)蔚鸥,通過成員變量isa保持該類的結構體實例指針惜论。
圖2-4oc類與對象的實質.png

各類的結構體是基于objc_class結構體的class_t結構體:

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結構體實例以及NSMutableArray的class_t結構體實例等止喷,均生成并保持各個類的class_t結構體實例馆类。
    • 該實例持有聲明的成員變量、方法的名稱弹谁、方法的實現(xiàn)(即函數(shù)指針)蹦掐、屬性以及父類的指針技羔,并被Objective-C運行時庫所使用。
  • 回到正題——“Block就是Objective-C對象”卧抗,*** 先看Block結構體:***
struct __main_block_impl_0 {
    void *isa;
    int Flags;      // 標志
    int Reserved;   // 今后版本升級所需的區(qū)域
    void *FuncPtr;  // 函數(shù)指針
    struct __main_block_desc_0* Desc;
};

理解:

  • 此__main_block_impl_0結構體相當于基于objc_object結構體的Objective-C類的對象的結構體。
  • 對其中的isa進行初始化鳖粟,如
isa = &_NSConcreteStackBlock;

即_NSConcreteStackBlock相當于class_t結構體實例

  • 在將Block作為Objective-C的對象處理時社裆,關于該類的信息放置于_NSConcreteStackBlock中。

2.3.2 獲取自動變量值

截獲自動變量值的源代碼經(jīng)clang轉換向图,如下:

// 結構體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    const char *fmt; // Block語法表達式“使用的自動變量”被追加到該結構體
    int val;
    
    // 構造函數(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結構體實例(即Block)所截獲的自動變量在Block語法表達式執(zhí)行之前就被聲明定義泳秀,所以,在Objective-C的源代碼中榄攀,執(zhí)行Block語法表達式時無需改動便可使用截獲的自動變量值嗜傅。
    2. "截獲自動變量值"意味著在執(zhí)行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構體實例(即Block自身)中檩赢。
    3. Block不能直接使用“C語言數(shù)組類型的自動變量”吕嘀,所以,截獲自動變量時贞瞒,會將其值傳遞給結構體的構造函數(shù)進行保存
    */
    
    printf(fmt, val);
}

// 靜態(tài)結構體 __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()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    // 調用結構體__main_block_impl_0的構造函數(shù)初始化該結構體實例
    void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
    /*
    理解:
    1. 在初始化結構體實例時,會根據(jù)傳遞給構造函數(shù)的參數(shù)對由自動變量追加的成員變量進行初始化(即執(zhí)行Block語法使用的自動變量fmt和val會初始化結構體實例)
    2. __main_block_impl_0結構體實例的初始化如下:
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
      fmt = "val = %d\n";
      val = 10;
    3. 由上可知军浆,在__main_block_impl_0結構體實例(即Block)中棕洋,自動變量被截獲。
    */
    
    return 0;
}

2.3.3 __block說明符

截獲自動變量值的源代碼經(jīng)clang轉換乒融,如下:

// 結構體 __block_impl
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    const char *fmt; // Block語法表達式“使用的自動變量”被追加到該結構體
    int val;
    
    // 構造函數(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結構體實例(即Block)所截獲的自動變量在Block語法表達式執(zhí)行之前就被聲明定義掰盘,所以,在Objective-C的源代碼中赞季,執(zhí)行Block語法表達式時無需改動便可使用截獲的自動變量值愧捕。
    2. "截獲自動變量值"意味著在執(zhí)行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構體實例(即Block自身)中碟摆。
    3. Block不能直接使用“C語言數(shù)組類型的自動變量”晃财,所以,截獲自動變量時典蜕,會將其值傳遞給結構體的構造函數(shù)進行保存
    */
    
    printf(fmt, val);
}

// 靜態(tài)結構體 __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()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    // 調用結構體__main_block_impl_0的構造函數(shù)初始化該結構體實例
    void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
    /*
    理解:
    1. 在初始化結構體實例時,會根據(jù)傳遞給構造函數(shù)的參數(shù)對由自動變量追加的成員變量進行初始化(即執(zhí)行Block語法使用的自動變量fmt和val會初始化結構體實例)
    2. __main_block_impl_0結構體實例的初始化如下:
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
      fmt = "val = %d\n";
      val = 10;
    3. 由上可知愉舔,在__main_block_impl_0結構體實例(即Block)中钢猛,自動變量被截獲。
    */
    
    return 0;
}

2.3.4 Block存儲域

從“__block說明符”一節(jié)中可知轩缤,*** Block轉換為Block的結構體類型的自動變量,___block變量轉換為__block的結構體類型的自動變量命迈。所謂結構體類型的自動變量贩绕,即棧上生成的該結構體的實例。 ***

表 Block與__block變量的實質

名稱 實質
Block 棧上block的結構體實例
_block變量 棧上_block變量的結構體實例
  • 從“Block的實質”一節(jié)中可知壶愤,Block是Objective-C對象淑倾,并且該Block的類為_NSConcreteStackBlock。此外征椒,與之類似的還有兩個類:

  • _NSConcreteStackBlock —— *** 該類對象設置在棧上 ***

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

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

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


圖2-6 設置為Block的存儲域.png

注意

  • 由于_NSConcreteGlobalBlock類生成的Block對象設置在程序的數(shù)據(jù)區(qū)域中(該類會運用在“全局變量”的聲明中)娇哆,由此該類的結構體實例的內(nèi)容不依賴于執(zhí)行時的狀態(tài),所以整個程序只需一個實例勃救。
  • 只在截獲自動變量時碍讨,Block的結構體實例截獲的值才會根據(jù)執(zhí)行時的狀態(tài)變化,而在不截獲自動變量時蒙秒,Block的結構體實例每次截獲的值都相同勃黍。也就是說,即時在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時晕讲,只要Block不截獲自動變量覆获,就可以將Block的結構體實例設置在程序的數(shù)據(jù)區(qū)域。

總結

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

    • 記述全局變量的地方有Block語法時
    • Block語法表達式中不使用“應截獲的自動變量”時
  • 除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象(即類對象設置在棧上)益兄。

  • 而锻梳,_NSConcreteMallocBlock類是Block超出變量作用域可存在的原因。

遺留問題

“__block說明符”一節(jié)中遺留的問題:

  • Block超出變量作用域可存在的原因
  • __block變量的結構體成員變量__forwarding存在的原因

配置在全局變量的Block净捅,從變量作用域外也可以通過指針安全的使用疑枯,而配置在棧上的Block,如果其所屬的變量作用域結束蛔六,該Block就被廢棄荆永。如圖:

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

為此,Blocks提供了將Block和__block變量從棧上復制到堆上的方法來解決這個問題国章。

如圖:

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

實現(xiàn)機制:

  • 復制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block的結構體實例的成員變量isa具钥。
impl.isa = &_NSConcreteMallocBlock;

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

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

實際上骂删,ARC有效時,大多數(shù)編譯器會恰當?shù)剡M行判斷四啰,自動生成將Block從棧上復制到堆上的代碼宁玫。

typedef int (^blk_t)(int);
blk_t func(int rate)
{
  return ^(int count){return rate * count;};
}

該源代碼中的函數(shù)會返回配置在棧上的Block。即當程序執(zhí)行從該函數(shù)返回函數(shù)調用方時柑晒,變量作用域結束欧瘪,因此棧上的Block也被廢棄。雖然有這樣的問題匙赞,但該源代碼通過對應ARC的編譯器可轉換如下:

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有效時佛掖,blk_t tmp 相當于blk_t __strong tmp.
  • objc_retainBlock實際上是Block_copy函數(shù)妖碉。
    詳細的注釋:
blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
  
  /*
   * 將通過Block語法生成的Block(即配置在棧上的Block結構體實例)
   * 賦值給相當于Block類型的變量tmp
   */
  
  tmp = _Block_copy(tmp);
  
  /*
   * _Block_copy函數(shù)
   * 將棧上的Block復制到堆上
   * 復制后,將堆上的地址作為指針賦值給變量tmp
   */
  
  return objc_autoreleaseReturnValue(tmp);
  
  /*
   * 將堆上的Block作為Objective-C對象
   * 注冊到autoreleasepool中芥被,然后返回該對象
   */
}

*** 將Block作為函數(shù)返回值返回時欧宜,編譯器會自動生成復制到堆上的代碼。 ***

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

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

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

*** 對Block語法調用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í)行時發(fā)生異常职员,應用程序強制結束。這是由于*** getBlockArray函數(shù)執(zhí)行結束時跛溉,棧上的Block被廢棄的緣故焊切。 *** 此時,編譯器不能判斷是否需要復制芳室。也可以不讓編譯器進行判斷专肪,而使其在所有情況下都能復制。但將Block從棧上復制到堆上是相當消耗CPU的堪侯。當Block設置在棧上也能夠使用時嚎尤,將Block從棧上復制到堆上只是在浪費CPU資源。因此只在此情形下讓編程人員手動進行復制伍宦。

對源代碼修改一下芽死,便可正常運行:

- (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();

*** 對Block類型變量調用copy方法 ***

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

表 Block的副本

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

不管Block配置在何處次洼,用copy方法復制都不會引起任何問題关贵。在不確定時調用copy方法即可。

在ARC有效時卖毁,多次調用copy方法完全沒有問題

blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過多次復制揖曾,變量blk仍然持有Block的強引用,該Block不會被廢棄亥啦。

2.3.5 __block變量存儲域

從“Block存儲域”一節(jié)可知炭剪,*** 使用__block變量的Block從棧復制到堆上時,__block變量也會受到影響禁悠。 ***

表 Block從棧復制到堆時對__block變量產(chǎn)生的影響

__block變量的配置存儲域 Block從棧復制到堆時的影響
從棧復制到堆并被Block持
被Block持有

*** 在一個Block中使用__block變量 ***

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

*** 在多個Block中使用__block變量 ***

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

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

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

遺留的問題

“Block存儲域”一節(jié)中遺留的問題:

  • 使用__block變量的結構體成員變量__forwarding的原因

不管__block變量配置在棧上還是在堆上念祭,都能夠正確地訪問該變量

正如這句話所訴,通過Block的復制碍侦,__block變量也會從棧復制到堆上粱坤。此時可同時訪問棧上的__block變量和堆上的__block變量隶糕。

__block int val = 0;  // __block變量

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

++val;

blk();

NSLog(@"%d", val);

利用copy方法復制使用了__block變量的Block語法。此時站玄,Block和__block變量均從棧復制到堆枚驻。

*** 在Block語法表達式中,使用初始化后的__block變量 ***

^{++val;}

*** 在Block語法表達式之后株旷,使用與Block無關的__block變量 ***

++val;

然而再登,以上兩種源代碼都可以轉換為:

++(val.__forwaring->val);

在變化Block語法的函數(shù)中,該變量val為 *** 復制到堆上的__block變量的結構體實例 晾剖,而使用與Block無關的變量val锉矢,為 復制前棧上的__block變量的結構體實例 ***。

但是齿尽,棧上的__block變量的結構體實例(即變量val)在__block變量從棧復制到堆上時沽损,會將成員變量__forwarding的值替換為復制目標堆上的__block變量的結構體實例的地址。

圖2-12 復制__block變量.png

2.3.6 截獲對象

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

該源代碼生成并持有NSMutableArray類的對象循头,但是附有__strong修飾符的賦值目標(變量array)變量作用域立即就會結束绵估,因此對象被立即釋放并廢棄。

blk_t blk;

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

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


變量作用域結束的同時卡骂,變量array被廢棄国裳,其對NSMutableArray類的對象的強引用失效,因此NSMutableArray類的對象被釋放并廢棄(此處我不確定是否會被廢棄)全跨。但是缝左,該源代碼運行正常,執(zhí)行結果如下:

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

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

經(jīng)clang轉換:

/* Block的結構體 / 函數(shù)部分  */

// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
    // 成員變量
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    id __strong array; 
    /*
    理解:
    1. 被NSMutableArray類對象并被截獲的自動變量array盒使,是附有__strong修飾符的成員變量。在Objective-C中七嫌,C語言結構體不能含有附有__strong修飾符的變量少办。因為編譯器不知道何時進行C語言結構體的初始化和廢棄操作,不能很好地管理內(nèi)存诵原。
    2. 但是英妓,Objective-C的運行時庫能準確把握Block從棧復制到堆以及堆上的Block被廢棄的時機,因此Block的結構體即時含有附有__stong修飾符或__weak修飾符的變量绍赛,也可以恰當?shù)剡M行初始化和廢棄蔓纠。
    */
    
    // 構造函數(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ù)將“對象類型對象”賦值給Block的結構體成員變量array中并持有該對象
    2. _Block_object_assign函數(shù)調用“相當于ratain實例方法的函數(shù)”,將“對象”賦值在對象類型的結構體成員變量中吗蚌。
    */
}

// 靜態(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的結構體成員變量array中的對象。
    2. _Block_object_dispose函數(shù)調用相當于release實例方法的函數(shù)蚯妇,釋放賦值在對象類型的結構體成員變量中的對象敷燎。
    */
}

// 靜態(tài)結構體 __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結構體成員變量copy和dispose中暂筝,但是在轉換后的源代碼中,這些函數(shù)包括使用指針全都沒有被調用硬贯。
2. 而是焕襟,在Block從棧復制到堆時以及堆上的Block被廢棄時會調用這些函數(shù)。
*/

/* Block語法饭豹,使用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]);

表 調用copy函數(shù)和dispose函數(shù)的時機

函數(shù) 調用時機
copy函數(shù) 棧上的Block復制到堆時
dispose函數(shù) 堆上的Block被廢棄時

*** 何時棧上的Block會復制到堆 ***

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

在棧上的Block被復制到堆時鸵赖,copy函數(shù)被調用,而在釋放復制到堆上的Block后拄衰,誰都不持有Block而被廢棄時它褪,dispose函數(shù)被調用。正因為這種構造翘悉,通過使用附有__strong修飾符的自動變量列赎,Block中截獲的對象才能給超出其變量作用域而存在。

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

表 截獲對象時和使用__block變量時的不同

對象 BLOCK_FIELD_IS_OBJECT
__block變量 BLOCK_FIELD_IS_BYREF
  • 通過BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF參數(shù)镐确,區(qū)分copy函數(shù)和dispose函數(shù)的對象類型是對象還是__block變量。

  • 但是饼煞,與copy函數(shù)持有被截獲的對象源葫,dispose函數(shù)釋放截獲的對象相同,copy函數(shù)持有所使用的__block變量砖瞧,dispose函數(shù)釋放所使用的__block息堂。

  • 由此可知,Block中使用的賦值給附有__stong修飾符的自動變量的對象和復制到堆上的__block變量块促,由于被堆上的Block所持有荣堰,因而可超出其變量作用域而存在。

*** 何種情形下竭翠,不調用Block的copy實例方法 ***
在Block使用對象類型自動變量是振坚,除以下情形外,推薦調用Block的copy實例方法:

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

2.3.7 __block變量和對象

*** 附加__strong修飾符的對象類型__block變量和自動變量***

__block說明符可指定“任何類型”的自動變量斋扰。

下面指定用于賦值Objective-C對象的id類型自動變量:

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

ARC有效時渡八,id類型以及對象類型變量默認附加__strong修飾符。
所以传货,該代碼等同于:

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

經(jīng)clang轉換:

/* __block變量的結構體部分 */

// 結構體 __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類型或對象類型自動變量”的情況下屎鳍,當Block從棧復制到堆時,使用_Block_object_copy函數(shù)问裕,持有Block截獲的對象逮壁。當堆上的Block被廢棄時,使用_Block_object_dispose函數(shù)粮宛,釋放Block截獲的對象窥淆。

在__block變量為“附有__strong修飾符的id類型或對象類型自動變量”的情形下會發(fā)生同樣的過程卖宠。當__block變量從棧復制到堆時,使用_Block_object_copy函數(shù)祖乳,持有賦值給__block變量的對象。當堆上的__block變量被廢棄時眷昆,使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對象作媚。

由此可知帅刊,即時對象賦值給“復制到堆上的附有__strong修飾符的對象類型__block變量”中,只要__block變量在堆上繼續(xù)存在女揭,那么該對象就會繼續(xù)處于被持有的狀態(tài)。這與在Block中對象賦值給“附有__strong修飾符的對象類型自動變量”相同吧兔。

*** 附有__weak修飾符的id類型自動變量 ***
在Block中使用附有__weak修飾符的id類型自動變量:

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í)行結果:

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

該段代碼能夠正常運行境蔼。這是因為在變量作用域結束時,附有__strong修飾符的自動變量array所持有的NSMutableArray類對象會被釋放被廢棄伺通,而附有__weak修飾符的自動變量weakArray由于對NSMutableArray類對象持有弱引用箍土,此時nil賦值在自動變量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í)行結果:

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

這段代碼也能正常運行罐监。這是因為即時附加了__block說明符吴藻,在變量作用域結束時,附有__strong修飾符的自動變量array所持有的NSMutableArray類對象會被釋放被廢棄弓柱,而附有__weak修飾符的自動變量blockWeakArray由于對NSMutableArray類對象持有弱引用调缨,此時nil賦值在自動變量blockWeakArray上。

注意

  • 由于附有__unsafe_unretained修飾符的變量只不過與指針相同吆你,所以不管在Block中使用還是附加到__block變量中弦叶,也不會像__strong修飾符或__weak修飾符那樣進行處理。在使用附有__unsafe_unretained修飾符的變量時妇多,注意不要通過懸掛指針訪問已被廢棄的對象伤哺,否則程序可能會崩潰!
  • 沒有設定__autoreleasing修飾符與Block同時使用。
  • __autoreleasing修飾符與__block說明符同時使用會產(chǎn)生編譯錯誤立莉。

2.3.8 Block循環(huán)引用

*** 如果在Block中使用附有__strong修飾符的對象類型自動變量绢彤,那么當Block從棧復制到堆時,該對象為Block所持有蜓耻。 *** 這樣容易引起*** 循環(huán)引用 ***茫舶。

*** 使用Block類型成員變量和附有__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;
}

編譯該源代碼時,編譯器會發(fā)出警告刹淌,這是因為出現(xiàn)了循環(huán)引用饶氏,從而導致dealloc實例方法沒有被調用。

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

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

- (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修飾符眉踱,并且不用擔心懸掛指針問題谈喳。

*** 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;
}


該源代碼,如果編譯挽绩,編譯器會發(fā)出警告(出現(xiàn)循環(huán)引用)模聋。這是因為Block語法中使用的obj_實際上截獲了self链方,而對編譯器來說工窍,obj_只不過是對象的結構體的成員變量移剪。

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;
}


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

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

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

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

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

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

因此撕攒,__block變量blockSelf對MyObject類對象的強引用失效抖坪,從而避免了循環(huán)引用擦俐,如下圖:

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

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

優(yōu)點

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

缺點

為避免循環(huán)引用必須執(zhí)行Block
存在執(zhí)行了Block語法,卻不執(zhí)行Block的路徑時盲再,無法避免循環(huán)引用答朋。

總結

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

2.3.9 copy/release

*** ARC無效時,需要用copy實例方法手動將Blocl從棧復制到堆叉谜,用release實例方法來釋放復制的Block很钓。 ***

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

[blk_on_heap release];

*** 只要Block有一次復制并配置在堆上履怯,就可通過retain實例方法持有叹洲。***

[blk_on_heap retain];

*** 但是运提,對于配置在棧上的Block調用retain實例方法則不起任何作用。 ***

[blk_on_stack retain];

該源代碼中栈妆,雖然對賦值給blk_on_stack的棧上的Block調用了retain實例方法鳞尔,但實際上對此源代碼不起任何作用寥假。因此糕韧,推薦使用copy實例方法來持有Block(不用retain實例方法)萤彩。

由于Block是C語言的擴展额衙,所以在C語言中也可以使用Block語法窍侧。此時使用“Block_copy函數(shù)”和“Block_release函數(shù)”代替copy/release實例方法伟件。

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

Block_release(blk_on_heap);

*** ARC無效時斧账,__block說明符被用來避免Block中的循環(huán)引用。***
這是由于當Block從棧復制到堆時习绢,若Block使用的變量為附有__block說明符的id類型或對象類型的自動變量闪萄,不會被retain败去;若Block使用的變量為沒有__block說明符的id類型或對象類型的自動變量圆裕,則被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;
}

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

可使用__block變量來避免出現(xiàn)該問題晕鹊。

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


這時溅话,由于Block使用__block變量飞几,所以不會被retain屑墨。

注意

ARC有效時,__block說明符和__unsafe_unretained修飾符一起使用以躯,來解決附有__unsafe_unretained修飾符的自動變量不能retain的問題寸潦。

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末易核,一起剝皮案震驚了整個濱河市牡直,隨后出現(xiàn)的幾起案子碰逸,更是在濱河造成了極大的恐慌饵史,老刑警劉巖胳喷,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牵辣,死亡現(xiàn)場離奇詭異纬向,居然都是意外死亡逾条,警方通過查閱死者的電腦和手機师脂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啄育,“玉大人挑豌,你說我怎么就攤上這事侯勉≈访玻” “怎么了练对?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赠制。 經(jīng)常有香客問我烟号,道長汪拥,這世上最難降的妖魔是什么迫筑? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮辕棚,結果婚禮上逝嚎,老公的妹妹穿的比我還像新娘补君。我一直安慰自己赚哗,他們只是感情好屿储,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布够掠。 她就那樣靜靜地躺著疯潭,像睡著了一般哭廉。 火紅的嫁衣襯著肌膚如雪遵绰。 梳的紋絲不亂的頭發(fā)上椿访,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音拳喻,去河邊找鬼荣病。 笑死个盆,一個胖子當著我的面吹牛颊亮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雹有,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼质帅!你這毒婦竟也來了煤惩?” 一聲冷哼從身側響起魄揉,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瓣俯,失蹤者是張志新(化名)和其女友劉穎在旱,沒想到半個月后桶蝎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體噪服,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年雹顺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片没酣。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖闪金,靈堂內(nèi)的尸體忽然破棺而出哎垦,到底是詐尸還是另有隱情囱嫩,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布漏设,位于F島的核電站墨闲,受9級特大地震影響,放射性物質發(fā)生泄漏郑口。R本人自食惡果不足惜鸳碧,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犬性。 院中可真熱鬧套利,春花似錦喊衫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肛冶。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沥匈。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓砰逻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355