讀書筆記-《Objective-C高級編程》之Blocks

2.1 Blocks概要

2.1.1 什么是Blocks

Blocks是C語言的擴(kuò)充功能懊渡。是帶有自動變量(局部變量)的匿名函數(shù)遭商。

匿名函數(shù)

C語言的函數(shù)

    // 聲明函數(shù)名稱為func的函數(shù)
    int func(int count);
    // 通過函數(shù)名稱func調(diào)用函數(shù)
    int result1 = func(10);
    // 將函數(shù)func的地址賦值給指針類型變量funcptr
    int (*funcptr)(int) = &func;
    // 通過函數(shù)指針調(diào)用函數(shù)
    int result2 = (*funcptr)(10);

C語言的函數(shù)都會必須有函數(shù)的名稱,而通過Blocks蚜枢,源代碼中能夠使用匿名函數(shù),即不帶名稱的函數(shù)。

帶有自動變量

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

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

其中主届,在函數(shù)的多次調(diào)用之間能夠傳遞值的變量有:

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

雖然這些變量的作用域不同,但在整個程序中待德,一個變量總保持在一個內(nèi)存區(qū)域君丁。因此,雖然多次調(diào)用函數(shù)将宪,但總量值總能保持不變绘闷,在任何時候以任何狀態(tài)調(diào)用,使用的都是同樣的變量的值较坛。

Blocks提供了保持自動變量值的方法印蔗。

2.2 Blocks模式

2.2.1 Block語法

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

(1) 沒有函數(shù)名

(2) 帶有“^”丑勤。

Block語法BN范式

    Block_literal_expression ::= ^ block_decl compound_statement_body
    
    block_decl ::=
    
    block_decl ::= parameter_list
    
    block_decl ::= type_expression
Block語法

??

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

返回值類型可以省略华嘹。如果聲明返回值類型,返回值類型需要跟表達(dá)式中return語句中的返回值類型一致法竞,如果表達(dá)式中無return語句可以使用void類型耙厚。如果表達(dá)式中有多個return語句强挫,所有return的返回值類型必須相同。

Block語法省略返回值類型

??

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

如果不使用參數(shù)颜曾,參數(shù)列表也可以省略纠拔。

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

??

    ^{printf("Blocks");}

2.2.2 Block類型變量

在C語言中,可以將所定義的函數(shù)的地址賦值給函數(shù)指針類型的變量泛豪。同樣地稠诲,在Block語法下,可將Block語法賦值給聲明的Block類型的變量中诡曙。

聲明Block類型變量的示例如下:

    int (^blk)(int);

該Block類型變量與一般C語言變量完全相同臀叙,可作為一下用途使用:

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

使用Block語法將Block賦值為Block類型變量

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

變量blk與通常的變量相同,所以也可以由Block類型變量向Block類型變量賦值价卤,可以向函數(shù)傳遞Block劝萤,也可以將Block作為函數(shù)的返回值。在函數(shù)參數(shù)和返回值中使用Block類型變量時慎璧,記述方式極為復(fù)雜床嫌,我們可以像使用函數(shù)指針類型時那樣,使用typedef來解決該問題胸私。

// 定義一個返回值類型是int 別名為blk_t 有一個int類型參數(shù) 的Block
typedef int (^blk_t)(int);

Block類型的變量可完全像通常的C語言變量一樣使用厌处,因此也可以使用指向Block類型變量的指針,即Block指針類型變量岁疼。

    // C語言中可以這么使用
    typedef int (^blk_t)(int);
    blk_t blk = ^(int count){return count + 1;};
    blk_t *blkptr = &blk;
    (*blkptr)(10);

2.2.3 截獲自動變量的值

Blocks中阔涉,Block表達(dá)式截獲所使用的自動變量的值是自動變量的瞬間值(保存在Block結(jié)構(gòu)體中)。

int main(int argc, const char * argv[]) {
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val);};
    
    val = 2;
    fmt = "These values were changed. val = %d\n";
    
    blk();
    
    return 0;
}

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

val = 10

2.2.4 __block說明符

自動變量的值截獲只能保存執(zhí)行Block語法的瞬間值捷绒。保存后就不能改寫該值瑰排。

若想在Block語法的表達(dá)式中將值賦給在Block語法外聲明的自動變量,需要在該自動變量上加__block說明符暖侨。

2.2.5 截獲的自動變量

賦值給截獲的自動變量的操作會產(chǎn)生編譯錯誤椭住,但使用截獲的值卻不會有任何問題。

賦值操作

    id array = [NSMutableArray array];
    
    void (^blk)(void) = ^{
        array = [NSMutableArray array];
    };
    
    blk();

編譯器報錯

Variable is not assignable (missing __block type specifier)

使用操作

    id array = [NSMutableArray array];
    
    void (^blk)(void) = ^{
        [array addObject:@1];
    };
    
    blk();

編譯器編譯成功字逗。

需要注意的是現(xiàn)在的Blocks中函荣,截獲自動變量的方法并沒有實(shí)現(xiàn)對C語言數(shù)組的截獲,這時使用指針可以解決該問題扳肛。

?

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

error

Cannot refer to declaration with an array type inside block

    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ì)

我們可以通過clang編譯器將含有Block語法的源代碼轉(zhuǎn)換為C++源代碼傻挂。

cd到目標(biāo)文件夾,執(zhí)行語句

clang -rewrite-objc 源代碼文件名

源碼

#include <stdio.h>

int main()
{
    void (^blk)(void) = ^{
        printf("Block\n");
    };
    
    blk();
    
    return 0;
}

該源碼可通過clang轉(zhuǎn)換為以下形式:

// 結(jié)構(gòu)體 &:取地址運(yùn)算符 *:指針運(yùn)算符
struct __block_impl {
  void *isa;
  int Flags;    // 標(biāo)志
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

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

    return 0;
}

我們來一步步解讀上面的轉(zhuǎn)換后的代碼

源代碼的Block語法

^{printf("Block\n");}

轉(zhuǎn)換為

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
    }

可以看出Blocks使用的匿名函數(shù)實(shí)際被作為C語言函數(shù)來處理挖息,根據(jù)Block語法所屬的的函數(shù)名(main)和Block語法在該函數(shù)出現(xiàn)的順序值(0)來給clang變換的函數(shù)命名金拒。

該函數(shù)的參數(shù)__self相當(dāng)于C++實(shí)例方法中指向?qū)嵗陨淼淖兞縯his,或是Objective-C實(shí)例方法中指向?qū)ο笞陨淼淖兞縮elf,即參數(shù)__self為指向Block值的變量绪抛。

這里的參數(shù)__self__main_block_impl_0結(jié)構(gòu)體的指針资铡。

綜上,Block結(jié)構(gòu)體就是__main_block_impl_0結(jié)構(gòu)體幢码。Block的值就是通過__main_block_impl_0構(gòu)造出來的笤休。

__main_block_impl_0結(jié)構(gòu)體

struct __main_block_impl_0 {
  struct __block_impl impl; // 成員變量
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0結(jié)構(gòu)體有成員變量impl、Desc和構(gòu)造函數(shù)三部分組成症副。

__block_impl結(jié)構(gòu)體

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

__block_impl結(jié)構(gòu)體成員變量如下:

  • isa:
  • Flags:標(biāo)志
  • Reserved:今后版本升級所需的區(qū)域
  • *FuncPtr:函數(shù)指針

__main_block_desc_0結(jié)構(gòu)體

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} 

__main_block_desc_0結(jié)構(gòu)體成員變量如下:

  • reserved:今后版本升級所需的區(qū)域
  • Block_size: Block的大小

初始化__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

__main_block_imlp_0結(jié)構(gòu)體就相當(dāng)于Objective-C類對象的結(jié)構(gòu)體店雅,這里的_NSConcreteStackBlock相當(dāng)于Block的結(jié)構(gòu)體實(shí)例,也就是說block其實(shí)就是Objective-C對于閉包的對象實(shí)現(xiàn)。

Objective-C中由類生成對象就是像結(jié)構(gòu)體這樣“生成由該類生成的對象的結(jié)構(gòu)體實(shí)例”贞铣。生成的各個對象闹啦,即由該類生成的對象的各個結(jié)構(gòu)體實(shí)例,通過成員變量isa保持該類對的結(jié)構(gòu)體實(shí)例指針辕坝。

Objective-C類與對象的實(shí)質(zhì)

各類的結(jié)構(gòu)體是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體窍奋。class_t結(jié)構(gòu)體在objc4運(yùn)行時庫的runtime/objc-runtime-new.h中聲明如下:

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í)例等酱畅,均生成并保持各個類的class_t結(jié)構(gòu)體實(shí)例琳袄。該實(shí)例持有聲明的成員變量、成員的名稱纺酸、方法的實(shí)現(xiàn)(即函數(shù)指針)窖逗、屬性、以及父類的指針吁峻,并被Objective-C運(yùn)行時庫所使用。

如果展開__main_block_impl_0結(jié)構(gòu)體的__block_impl結(jié)構(gòu)體在张,可記述為如下形式:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *Funcptr;
  struct __main_block_desc_0* Desc;
};

該結(jié)構(gòu)體構(gòu)造函數(shù)會像下面這樣進(jìn)行初始化

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
Funcptr = __main_block_impl_0用含;
Desc = & __main_block_desc_0_DATA;

__main_block_impl_0結(jié)構(gòu)體相當(dāng)于基于objc_object結(jié)構(gòu)體的Objective-C類對象的結(jié)構(gòu)體_NSConcreteStackBlock相當(dāng)于class_t結(jié)構(gòu)體實(shí)例。在將Block作為Objective的對象處理時帮匾,關(guān)于該類的信息放置于_NSConcreteStackBlock中啄骇。

Block實(shí)質(zhì)就是對象。

2.3.2 截獲自動變量值

源碼

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void(^blk)(void) = ^{printf(fmt,val);};
    
    val = 2;
    fmt = "These values were changed. val = %d\n";
    
    blk();
    
    return 0;
}

該源碼可通過clang轉(zhuǎn)換為以下形式:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
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);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_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";
    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

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

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

    return 0;
}

我們可以看到Block語法表達(dá)式中使用的自動變量被作為成員變量追加到__main_block_impl_0結(jié)構(gòu)體中瘟斜。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
}

結(jié)構(gòu)體內(nèi)聲明的成員變量類型與自動變量類型完全相同缸夹,而Block語法表達(dá)式中沒有使用的自動變量不會被追加,如源代碼中的變量dmy螺句。

總的來說虽惭,所謂“截獲自動變量的值”意味著在執(zhí)行Block語法時,Block語法表達(dá)式所使用的自動變量的值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中蛇尚。

2.3.3 __block說明符

當(dāng)我們嘗試像下面這種方式改變Block中的自動變量val時

    int val = 0;
    void(^blk)(void) = ^{val = 1;};

因?yàn)樵趯?shí)現(xiàn)上不能改寫被截獲的自動變量的值芽唇,所以編譯器會報錯

Variable is not assignable (missing __block type specifier)

而我們有時需要在Block中改變外部變量的值,有兩種解決方法。

第一種

C語言中有一個變量匆笤,允許Block改寫值研侣。具體如下:

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

雖然Block語法的匿名函數(shù)部分簡單地變換為C語言函數(shù),但從這個變換的函數(shù)訪問靜態(tài)全局變量/全局變量沒有任何改變炮捧,可直接使用庶诡。

但是靜態(tài)變量的情況下,轉(zhuǎn)換后的函數(shù)原本就設(shè)置在含有Block語法的函數(shù)外(使用靜態(tài)變量的指針進(jìn)行訪問)咆课,所以無法從變量作用域訪問末誓。

實(shí)際上,在由Block語法生成的值Block上傀蚌,可以存有超過其變量作用域的被截獲對象自動變量基显。變量作用域結(jié)束的同時,原來的自動變量被廢棄善炫,因此Block中超過變量作用域而存在的變量同靜態(tài)變量一樣撩幽,將不能通過指針訪問原來的自動變量。

第二種

使用__block說明符

C語言中有以下存儲類說明符:

  • typedef
  • extern
  • static
  • auto
  • register

__block說明符類似于static箩艺、auto窜醉、register說明符,他們用于指定將變量值設(shè)置到哪個存儲域中艺谆。例如榨惰,auto表示作為自動變量存儲在棧中,static表示靜態(tài)變量存儲在數(shù)據(jù)區(qū)中静汤。

當(dāng)我們在自動變量聲明上追加__block說明符

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

該源碼可通過clang轉(zhuǎn)換為以下形式:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
    __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
}

可以發(fā)現(xiàn)__block變量也同Block一樣變成了__Block_byref_val_0結(jié)構(gòu)體類型的自動變量琅催,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例。該變量初始化為1虫给,且這個值也出現(xiàn)在結(jié)構(gòu)體實(shí)例的初始化中藤抡,這意味著該結(jié)構(gòu)體持有相當(dāng)于原自動變量的成員變量。

該結(jié)構(gòu)體聲明如下:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

再看下__block變量賦值的代碼:

    ^{val = 1;}

轉(zhuǎn)化為

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
 (val->__forwarding->val) = 1;
}

__block變量賦值抹估,Block的__main_block_impl_0結(jié)構(gòu)體實(shí)例持有指向__block變量的__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針缠黍。

__Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding持有指向該實(shí)例自身的指針。通過成員變量__forwarding訪問成員變量val药蜻。(成員變量val是該實(shí)例自身持有的變量瓷式,它相當(dāng)于原自動變量。)

訪問__block變量

2.3.4 Block存儲域

Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量语泽,__block變量轉(zhuǎn)換為__block變量的結(jié)構(gòu)體類型的自動變量贸典。所謂結(jié)構(gòu)體類型的自動變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例踱卵。

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

名稱 實(shí)質(zhì)
Block 棧上Block的結(jié)構(gòu)體實(shí)質(zhì)
__block變量 棧上__block變量的結(jié)構(gòu)體實(shí)例

Block的類

設(shè)置對象的存儲域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域(.data區(qū))
_NSConcreteMallocBlock
設(shè)置Block的存儲域

_NSConcreteGlobalBlock

在記敘全局變量的地方使用Block語法時瓤漏,生成的Block為_NSConcreteGlobalBlock類對象。例如:

void (^blk)(void) = ^{printf("Global Block");};

int main()
{

該源碼通過聲明全局變量blk來使用Block語法。如果轉(zhuǎn)換源代碼蔬充,Block用結(jié)構(gòu)體的成員變量isa的初始化如下:

    impl.isa = & _NSConcreteGlobalBlock

該Block的類為_NSConcreteGlobalBlock類蝶俱。此Block即該Block用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域中。因?yàn)樵谑褂萌肿兞康牡胤讲荒苁褂米詣幼兞考⒙圆淮嬖趯ψ詣幼兞窟M(jìn)行截獲榨呆。由此Block用結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴于執(zhí)行時的狀態(tài),所以整個程序中只需一個實(shí)例庸队。因此將Block用結(jié)構(gòu)體實(shí)例設(shè)置在全局變量相同的數(shù)據(jù)區(qū)域即可积蜻。

即使在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時,只要Block語法不截獲自動變量彻消,就可以將Block設(shè)置在程序的數(shù)據(jù)區(qū)域竿拆。

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

以上兩種情況下,Block為_NSConcreteGlobalBlock類對象宾尚。即Block配置在程序的數(shù)據(jù)區(qū)域中丙笋。除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象,且設(shè)置在棧上煌贴。

_NSConcreteMallocBlock

配置在全局變量上的Block御板,從變量作用域外可以通過指針安全地使用。但設(shè)置在棧上的Block牛郑,如果其所屬的變量作用域結(jié)束怠肋,該Block就被廢棄。由于__block變量也配置在棧上淹朋,同樣地笙各,如果其所屬的變量作用域結(jié)束,則該__block變量也被廢棄础芍。

棧上的Block與__block變量

Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法來解決這個問題杈抢。將配置在棧上的Block復(fù)制到堆上,這樣即使Block語法記述的變量作用域結(jié)束者甲,堆上的Block還可以繼續(xù)存在春感。

從棧復(fù)制到堆上的Block和__block變量

復(fù)制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block用結(jié)構(gòu)體實(shí)例的成員變量isa砌创。

    impl.isa = & _NSConcreteMallocBlock;

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

除了向方法或函數(shù)的參數(shù)傳遞Block時,編譯器都會自動生成將Block從棧上復(fù)制到堆上的代碼嫩实。

但是如果在方法或函數(shù)中適當(dāng)?shù)貜?fù)制了傳遞過來的參數(shù)刽辙,那么就不必在調(diào)用該方法或函數(shù)前手動復(fù)制了。以下方法或函數(shù)不用手動復(fù)制甲献。

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

將Block從棧上復(fù)制到堆上是相當(dāng)消耗COU的宰缤,當(dāng)Block設(shè)置在棧上也能夠使用時,將Block復(fù)制到堆上只是在浪費(fèi)CPU資源。因此只在block作為參數(shù)傳遞時手動調(diào)用copy方法慨灭。

Block副本

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

2.3.5 __block變量存儲域

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

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

若在1個Block中使用__block變量朦乏,Block從棧上復(fù)制到堆上,這些__block變量也全部從棧復(fù)制到堆上氧骤。此時呻疹,Block持有__block變量。Block已復(fù)制到堆上的情形下筹陵,復(fù)制Block對所使用的__block變量沒有任何影響刽锤。

在一個Block鐘使用__block變量

在多個Block中使用__block變量,任何一個Block從棧復(fù)制到堆時朦佩,__block變量也會一并從棧復(fù)制到堆并被該Block所持有并思。當(dāng)剩下的Block從棧復(fù)制到堆時,被復(fù)制的Block持有__block變量语稠,并增加__block變量的引用計數(shù)宋彼。

在多個Block鐘使用__block變量

如果配置在堆上的Block被廢棄,那么它所使用的__block變量就被釋放颅筋。

Block廢棄和__block變量的釋放

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

通過Block的復(fù)制宙暇,__block變量從棧復(fù)制到堆。此時可同時訪問棧上的__block變量和堆上的__block變量议泵。源代碼如下:

    __block int val = 0;
    void (^blk)(void) = [^{++val;} copy];
    ++val;
    blk();

利用copy方法復(fù)制了使用了__block變量的Block語法占贫。Block和__block變量均是從棧復(fù)制到堆。此代碼中在Block語法表達(dá)式中使用了初始化的__block變量先口。

    ^{++val;}

然后在Block語法之后使用了與Block無關(guān)的變量型奥。

    ++val;

以上兩種源代碼均可轉(zhuǎn)換為如下的形式:

    ++(val.__forwarding->val);

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

復(fù)制__block變量

這樣厢汹,無論是在Block語法中、Block語法外面使用__block變量谐宙,還是__block變量配置在椞淘幔或堆上,都可以順利地訪問同一個__block變量凡蜻。

2.3.6 截獲對象

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

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

Block會復(fù)制到堆上的時機(jī):

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

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

對象 BLOCK_FIELD_IS_OBJECT
__block變量 BLOCK_FIELD_IS_BYREF

Block中使用對象類型自動變量時搭综,除以下情景外,推薦調(diào)用Block的copy實(shí)例方法划栓。

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

2.3.7 __block變量和對象

__block說明符可指定任何類型的自動變量兑巾。

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

等同于

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

ARC有效時,id類型以及對象類型變量必定附加所有權(quán)修飾符忠荞,缺省為附有__strong修飾符的變量蒋歌。

__block變量為附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下帅掘,當(dāng)__block變量從棧復(fù)制到堆上時,使用_Block_object_assign函數(shù)持有賦值給__block變量的對象堂油。當(dāng)堆上的__block變量被廢棄時修档,使用__Block_object_dispose函數(shù),釋放賦值給__block變量的對象府框。

由此可知萍悴,即使對象賦值復(fù)制到堆上的附有__strong修飾符的對象類型__block中,只要__block變量在堆上繼續(xù)存在寓免,那么該對象就回繼續(xù)處于被持有的狀態(tài)癣诱。這與Block中使用賦值給附有__strong修飾符的對象類型自動變量的對象相同。

2.3.8 Block循環(huán)引用

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

typedef void(^blk_t)(void);

@interface MyObject : NSObject
{
    blk_t blk_;
}

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

該源代碼中MyObject中dealloc實(shí)例方法一定沒有被調(diào)用实抡。

MyObject類對象的Block類型成員變量blk_持有賦值為Block的強(qiáng)引用。即MyObject類對象持有Block欢策。init實(shí)例方法中執(zhí)行的Block語法使用附有__strong修飾符的id類型變量self吆寨。并且由于Block語法賦值在了成員變量blk_中,因此通過Block語法聲稱在棧上的Block此時由棧復(fù)制到堆踩寇,并持有所使用的self啄清。self持有Block,Block持有self俺孙。造成了循環(huán)引用辣卒。

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

為了避免此循環(huán)引用,可聲明附有__weak修飾符的變量睛榄,并將self賦值使用荣茫。

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

也可以使用__block變量來避免循環(huán)引用。

typedef void(^blk_t)(void);

@interface MyObject : NSObject
{
    blk_t blk_;
}

@implementation MyObject

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

- (void)execBlock {
    blk_();
}

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

int main()
{
    id o = [[MyObject alloc] init];
    [o execBlock];
    return 0;
}
@end

該源代碼沒有引起循環(huán)引用场靴。但是如果不調(diào)用execBlock實(shí)例方法啡莉,即不執(zhí)行賦值給成員變量blk_的Block,便會循環(huán)引用并引起內(nèi)存泄露旨剥。在生成并持有MyObject類對象的狀態(tài)會引起一下循環(huán)引用咧欣。

  • MyObject類對象持有Block
  • Block持有__block變量
  • __block變量持有MyObject類對象
循環(huán)引用

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

通過執(zhí)行execBlock實(shí)例方法该押,Block被執(zhí)行疗杉,nil被賦值在__block變量tmp中阵谚。

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

因此蚕礼,__block變量tmp對MyObject類對象的強(qiáng)引用失效。避免循環(huán)引用的過程如下所示:

  • MyObject類對象持有Block
  • Block持有__block變量
避免循環(huán)引用

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

  • 通過__block變量可控制對象的持有期間
  • 在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可(不必?fù)?dān)心懸垂指針)梢什。在執(zhí)行Block時可動態(tài)地決定是否將nil或其他對象賦值在__block變量中奠蹬。

使用__block變量的缺點(diǎn)如下:

  • 為避免循環(huán)引用必須執(zhí)行Block

2.3.9 copy/release

ARC無效時,一般需要手動將Block從棧復(fù)制到堆嗡午。另外囤躁,由于ARC無效,所以肯定要釋放復(fù)制的Block荔睹。這時我們用copy實(shí)例方法用來復(fù)制狸演,用release實(shí)例方法來釋放。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抬吟,更是在濱河造成了極大的恐慌算柳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雳攘,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)哨鸭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娇妓,“玉大人像鸡,你說我怎么就攤上這事」。” “怎么了坟桅?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕊蝗。 經(jīng)常有香客問我仅乓,道長,這世上最難降的妖魔是什么蓬戚? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任夸楣,我火速辦了婚禮,結(jié)果婚禮上子漩,老公的妹妹穿的比我還像新娘豫喧。我一直安慰自己,他們只是感情好幢泼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布紧显。 她就那樣靜靜地躺著,像睡著了一般缕棵。 火紅的嫁衣襯著肌膚如雪孵班。 梳的紋絲不亂的頭發(fā)上涉兽,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音篙程,去河邊找鬼枷畏。 笑死,一個胖子當(dāng)著我的面吹牛虱饿,可吹牛的內(nèi)容都是我干的拥诡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼氮发,長吁一口氣:“原來是場噩夢啊……” “哼渴肉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起爽冕,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤宾娜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扇售,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體前塔,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年承冰,在試婚紗的時候發(fā)現(xiàn)自己被綠了华弓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡困乒,死狀恐怖寂屏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娜搂,我是刑警寧澤迁霎,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站百宇,受9級特大地震影響考廉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜携御,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一昌粤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧啄刹,春花似錦涮坐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至昵时,卻和暖如春捷雕,著一層夾襖步出監(jiān)牢的瞬間椒丧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工非区, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盹廷。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓征绸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俄占。 傳聞我的和親對象是個殘疾皇子管怠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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