Obj-C高級編程--Blocks

Blocks

Blocks

Blocks 是帶有局部變量的匿名函數

截取自動變量值

int main()

{

? ? ? ? ? ? ? int dmy = 256;

? ? ? ? ? ? ? int val = 10;

? ? ? ? ? ? ? const char *fmt = "val = %d\n";

? ? ? ? ? ? ? void (^blk)(void) = ^void(void){printf(fmt, val);};

? ? ? ? ? ? ? val = 2;

? ? ? ? ? ? ? fmt = "There value were changed. val = %d\n";

? ? ? ? ? ? ? blk(); // 打?。簐al = 10

? ? ? ? ? ? ? ?return 0;

}

Block語法表達式使用fmtval。Blocks中,Block表達式截獲所使用的自動變量的值。即保存該自動變量的瞬間值。

因為Block表達式保存了自動變量的值,所以在執行Block語法后,即使改寫Block中使用的自動變量的值也不會影響Block執行時自動變量的值。

__block 說明符

局部變量附加了?__block?說明符,就能在 Block 內賦值。

C 語言數組加了 __block 仍是報錯。因為 Block 中獲取變量是賦值,C 數組不能直接賦值。

char test1[10];

char test2[10] = test1; // 報錯

Block 的實質

使用clang -rewrite-objc +源代碼文件名 看 C 語言源代碼。

int main()

{

? ? ? ? ? ?void (^blk)(void) = ^void(void){printf("Block\n");};

? ? ? ? ? ?blk();

? ? ? ? ? ? return 0;

}

通過 clang 轉為:

struct __block_impl {

? ? ? ? ? ?void *isa;

? ? ? ? ? ?int Flags;

? ? ? ? ? ?int Reserved;

? ? ? ? ? ?void *FuncPtr;

};

struct _main_block_impl_0 {

? ? ? ? ? ? struct __block_impl impl;

? ? ? ? ? ? struct _main_block_desc_0 *Des;? //結構體聲明

// 構造函數

? ? ? ? ? ? __main_block_imp_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 {

? ? ? ? ? ? ?unsigned long reserved;

? ? ? ? ? ? ?unsigned long Block_size;

} __main_block_desc_0_DATA = {

? ? ? ? ? ? ? 0,

? ? ? ? ? ? ? sizeof(struct __main_block_impl_0);

};

int main()

{

? ? ? ? ? ? ?void (*blk)(void) = (void (*)(void))&__main_block_impl0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

? ? ? ? ? ? ?(void (*)(struct __block_impl *))(stuct __block_impl *)blk)->FuncPtr)((struct __block_impl *) blk);

}

逐步分解,看源代碼中的 Block 語法。

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

變化后的源代碼中的表達式。

static void __main_block_func_0(struct __main_block_impl_0 * __cself)

{

? ? ? ? ? printf("Block\n");

}

通過Blocks使用的匿名函數實際上被作為簡單的 C 語言函數來處理。另外,根據Block 語法所屬的函數名(此處為mian)和該Block 語法在函數出現的順序值來給變換的函數命名。

參數__cself為指向 Block 值得變量。 即 __cself 是__main_block_imp_0結構體的指針。

struct __main_block_impl_0 {

? ? ? ? ?struct __block_impl impl;

? ? ? ? ?struct __main_block_desc_0 *desc;

}

第一個成員變量 impl,_block_impl 結構聲明

struct __block_impl {

? ? ? ? ? void *isa;

? ? ? ? ? int Flags;

? ? ? ? ? int Reserved;

? ? ? ? ? void *FuncPtr;

}

第二個成員變量 Desc 指針,_main_block_impl_0 結構聲明

struct __main_block_desc_0 {

? ? ? ? ? ? ?unsigned long reserved;

? ? ? ? ? ? ?unsigned long Block_size;

}

_main_block_impl_0 結構體的構造函數。

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

}

構造函數的調用:

void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_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;

源代碼將__main_block_impl_0結構體類型的自動變量,即棧上生成的__main_block_impl_0結構體實例的指針,賦值給__main_block_impl_0的結構體指針類型的變量 blk。

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

它等同于將 __main_block_impl_0 結構體實例的指針賦給變量 blk

源代碼的中的Block就是__main_block_impl_0結構體類型的局部變量,即棧上生成的__mainblockimpl_0結構體實例。

結構體實例的構造參數

__main_block_impl_0(__main_block_func_0, &__main_block_Desc_0_DATA);

第一個參數是由 Block 語法轉換的 C 語言函數指針

第二個參數是作為靜態全局變量的初始化的 __main_block_desc_0 結構體實例指針

__main_block_desc_0 結構體實例的初始化部分

static struct __main_block_desc_0 __main_block_desc_0_DATA = {

? ? ? ? sizeof(struct __main_block_impl_0)

}

__main_block_impl_0 結構體實例的大小,進行初始化。

該結構體初始化如下:

isa = &_NSConcreteStackBlock; // 該類 Block 放置于 Stack

Flags = 0;

Reserved = 0;

FuncPtr = __main_block_func_0;

Desc = &__main_block_desc_0_DATA;

使用改 Block 的部分

block()

轉化源代碼

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

去掉轉換部分

(*blk->impl.FuncPtr)(blk); // 使用函數指針調用函數

截取自動變量值

int main()

{

? ? ? ? ?int dmy = 256;

? ? ? ? ?int val = 10;

? ? ? ? ?const char *fmt = "val = %d\n";

? ? ? ? ?void (^blk)(void) = ^void(void){printf(fmt, val);};

? ? ? ? ? val = 2;

? ? ? ? ?fmt = "There value were changed. val = %d\n";

? ? ? ? ?blk(); // 打?。簐al = 10

? ? ? ? ?return 0;

}

clang 轉換

struct __main_block_impl_0 {

? ? ? ? ? ? struct __block_impl impl;

? ? ? ? ? ? struct __main_block_desc_0 *Desc;

? ? ? ? ? ? const char *fmt;//

? ? ? ? ? ? ?int val; // 將 Block 內部使用的變量追加到了結構體中

? ? ? ? ? ? ?__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;

? ? ? ? ? ? int val = __cself->val;

? ? ? ? ? ? printf(fmt,val);

}

static struct __main_block_desc_0 {

? ? ? ? ? ?unsigned long reserved;

? ? ? ? ? ?unsigned long 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) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);

? ? ? ? ? ?return 0;

}

Block 語法表達式中使用的自動變量作為成員變量追加到了 __main_block_impl_0,Block 語法表達式中沒有使用的自動變量沒有被追加。

執行 Block 語法時的自動變量 fmt 和 val 來初始化 __mainblockimpl_0 結構體實例。

初始化如下:

impl.isa = &_NSConcreteStackBlock;

impl.Flags = 0;

impl.FuncPtr = __main_block_func_0;

Desc = &__main_block_desc_0_DATA;

fmt = "val = %d\n";

val = 0;

所謂“截獲自動變量值”意味著在執行 Block 語法時,Block 語法表達式所使用的自動變量被保存到 Block 的結構體實例(即 Block 自身)中,在截獲自動變量時,將值傳遞給結構體的構造函數進行保存。

__block 說明符

Block 中所使用的被截獲的自動變量僅截獲自動變量的值。Block 中使用自動變量后,在 Block的結構體實例中重寫該自動變量也不會改變原來截獲的自動變量

改變自動變量的值有兩種方法:第一種:C 語言中有一個變量,允許 Block 改寫值。(靜態變量,靜態全局變量,全局變量)

雖然 Block 語法的匿名函數部分簡單地變換為了 C 語言函數,但從這個變化的函數中訪問靜態全局變量/全局變量并沒有任何改變,可直接使用。 但是靜態變量的情況下,轉換后的函數原本就設置在含有 Block 語法的函數外,所以無法從變量作用域訪問。

int global_val = 1;

static int static_global_val = 2;

int main()

{

? ? ? ? static int static_val = 3;

? ? ? ? void (^blk)(void) = ^ {

? ? ? ? ? ? ? ? global_val *= 1;?

? ? ? ? ? ? ? ? ?static_global_val *= 2;

? ? ? ? ? ? ? ? ?static_val *= 3;?

? ? ? ? ?};

? ? ? ? ? return 0;

}

該源代碼轉換后:

int global_val = 1;

static int _global_val = 2;

struct __main_block_impl_0 {

? ? ? ? ?struct __block_impl impl;

? ? ? ? ?struct __main_block_desc_0* Desc;

? ? ? ? ?int *static_val; // Block 中使用的自動變量

? ? ? ? ?__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;

? ? ? ? ? ?}

};

static void __main_block_func_0(struct __main_block_impl_0 * __cself) {

? ? ? ? ? ? ?int *static_val = __cself->static_val; // 使用靜態變量 static_val 的指針對其進行訪問,將靜態變量 static_val 的指針傳遞給 __main_block_impl_0 結構體的構造函數并保存。這是超出作用域使用變量的最簡單的方法。

? ? ? ? ? ?global_val *= 1;

? ? ? ? ? ?static_global val *= 2;

? ? ? ? ? ?(*static_val) *= 3;

}

static struct __main_block_desc_0 {

? ? ? ? ? ? unsigned long reserved;

? ? ? ? ? ?unsigned long Block_size;

} __main_block_desc_0_DATA = {

? ? ? ? ? ?0,

? ? ? ? ? ?sizeof(struct __main_block_impl_0)

}

int main()

{

? ? ? ? ? static int static_val = 3;

? ? ? ? ? blk = &__main_block_impl_0(__main_block_func_0, &__main_Block_desc_0_DATA, &static_val);

? ? ? ? ? return 0;

}

使用靜態變量static_val 的指針對其進行訪問,將靜態變量static_val 的指針傳遞給 _mainblockimpl0 結構體的構造函數并保存。這是超出作用域使用變量的最簡單的方法。

第二種方法使用“__block 說明符”。

__block int val = 10;

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

clang 編譯后

//__block修飾的變量結構體

struct __Block_byref_val_0 {

? ? ? ? void *_isa;

? ? ? ? __Block_byref_val_0 *__forwarding; // 指向結構體自己的 __Block_byref_val_0 結構體指針

? ? ? ? int __flags;

? ? ? ? int __size;

? ? ? ? int val; // 相當于自動變量的成員變量。值為10

};

struct __main_block_impl_0 {

? ? ? ?struct __block_impl impl;

? ? ? ?struct __main_block_desc_0 *Desc;

? ? ? ?__Block_byref_val_0 *val;// __block修飾的變量結構體指針 ? ? ?

? ? ? ?__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;

}

};

// Block 的 __main_block_impl_0 結構體實例持有指向 __block變量的

__Block_byref_val_0 結構體實例變量的指針。

static void __main_block_func_0(struct __main_block_impl_0 *__cself)

{

? ? ? ? ?__Block_byref_val_0 *val = __cself->val;

? ? ? ? ?(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(&dst->val, src-val, BLOCK_FIELD_IS_BYREF);

}

static void __main_block_dispose_0(struct __main_block_impl_0 *src)

{

? ? ? ? _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);

}

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

} __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 = {

? ? ? ? ? ? ? ? ? 0,

? ? ? ? ? ? ? ? ?&val,

? ? ? ? ? ? ? ? ?0,

? ? ? ? ? ? ? ? ?sizeof(__Block_byref_val_0),

? ? ? ? ? ? ? ? ?10

};

? ? ? ? blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);

? ? ? ? return 0;

}

加上__block的自動變量變為了結構體實例,_block變量也同 Block 一樣變成__Block_byref_val_0結構體類型的自動變量,即棧上生成的__Block_byref_val_0結構體實例。

__Block_byrefval0結構體實例的成員變量__forwarding持有指向該實例自身的指針。通過成員變量__forwwarding訪問成員變量 val。

Block 存儲域

Block 轉換為 Block 的結構體類型的自動變量,__block 變量轉換為 __block 變量的結構體類型的自動變量。

impl.isa有三種類型,即 Block 存儲位置有三種。

_NSConcreteStackBlock // 棧區

_NSConcreteGlobalBlock // 數據區

_NSConcreteMallocBlock // 堆區

記述全局變量的地方使用 Block 語法時,生成的 Block 為 NSConcreteGlobalBlock 類對象。

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

int main()

{

impl.isa = &_NSConreteGlobalBlock;此 Block 用結構體實例設置在程序的數據區域中。因為在使用全局變量的地方不能使用自動變量,所以不存在對自動變量進行截獲。由此 Block 用結構體實例的內容不依賴于執行時的狀態,所以整個程序中只需一個實例。因此將 Block 用結構體實例設置在與全局變量相同的數據區域中即可。

只要 Block 不截獲自動變量,就可以將 Block 用結構體實例設置在程序的數據區域。

存儲在 _NSConreteStackBlock 類對象:

記述全局變量的地方有 Block 語法時。

Block 語法的表達式中不適用應截獲的自動變量時。

配置在全局變量上的 Block,從變量作用域外也可以通過指針安全的使用。

但設置在棧上的 Block,如果其所屬的變量作用域結束,該 Block 就被廢棄。由于 __block 也配置在棧上,同樣的,如果其他所屬的變量作用域結束,則該 __變量也會被廢棄。

Blocks 提供了將 Block 和 __block 變量從棧上賦值到堆上的方法來解決這個問題。將配置在棧上的 Block 復制到堆上,這樣即使 Block 語法記述的變量作用域結束,堆上的 Block 還可以繼續存在。

復制到堆上的 Block 將 _NSConreteMallocBlock 類對象寫入 Block 用結構實例的成員變量 isa。impl.isa = _NSConreteMallocBlock

而 __block 變量用結構體成員變量 __forwarding 可以實現無論 __block 變量配置在棧上還是堆上時都能正確的訪問 __block 變量。有時在 __block 變量配置在堆上的狀態下,也可以訪問棧上的 __block 變量。在此情形下,只要棧上的結構體實例成員變量 __forwarding 指向堆上的結構體實例。不管堆上還是棧上都能訪問。

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

typedef int (^blk_t)(int);

blk_t func(int rate)

{

? ? ? ?return ^(int count){return rate * count;}; // 返回的 block 配置在棧上,即程序執行中從該函數返回函數調用時變量作用域結束,棧上的 block 也被廢棄。(ARC無效時)

}

//ARC 有效時

blkt func(int rate)

{

? ? ? ? ?blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);

? ? ? ? tmp = objc_retainBlock(tmp); // 實際上就是 Block_copy函數,將棧上的 Block 復制到堆上

? ? ? ? return objc_autoreleaseReturnValue(tmp); // 注冊到 autoreleasepool 中。

}

做題傳送門(關于 ACR和 MRC 中使用 Block 會不會崩潰)~傳送門

__block 變量存儲域

使用 __block 變量的 Block 從棧復制到堆上時,__block 變量也會受到影響。

若一個 Block 中使用 __block 變量,則當該 Block 從棧復制到堆時,使用的所有 __block 變量也必定配置在棧上。這些 __block 變量也全部被從棧復制到堆。此時,Block 持有 __block 變量。

若配置在堆上的 Block 被廢棄,那么它所使用的 __block 變量也就被釋放(它所持有的 __block 變量會被釋放)。

通過 Block 的復制,__block 變量也從棧復制到堆。此時,可以同時訪問棧上的 __block 變量和堆上 __block 變量。

__block int val = 0;

? ? ? ? void (^blk)(void) = [^{

? ? ? ? ++val;// 使用了堆上的 __block 變量

} copy]; // 利用 copy 將 Block 和 __block 變量復制到堆上

++val; // 使用了棧上的 __block 變量

blk();

NSLog(@"%d", val);

使用 val 都可以轉換為

++(val.__forwarding->val);

棧上的 __block 變量用結構體實例在 __block 變量從棧復制到堆上時,會將成員變量 __forwarding 的值替換為復制目標堆上的 __block 變量用結構體實例的地址。

截獲對象

附有 __strong 修飾符的賦值目標變量作用域立即結束,因此對象被立即釋放并廢棄。

{

? ? ? ? id array = [[NSMutableArray alloc] init];

} // 出了作用域對象被立即釋放

typedef int (^blk_t)(int);

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 count = 1

array count = 2

array count = 3

/* Block 用結構體 / 函數部分 */

struct __main_block_impl_0 {

? ? ?struct __block_impl impl;

? ? ? struct __main_block_desc_0 *Desc;

? ? ? ?id __strong array;

? ? ? ?__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;

? ? ? }

}

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

}

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

}

static void __main_block_dispose_0(struct __mian_block_impl_0 *src)

{

? ? ? ? ?_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);

}

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

} __main_block_desc_0_DATA = {

? ? ? ? 0,

? ? ? ? sizeof(struct __main_block_impl_0),

? ? ? ? __main_block_copy_0;? ? \用來管理結構體

? ? ? ?__main_block_dispose_0; /生命周期,初始化和廢棄。

}

/* Block 語法,使用 Block 部分 */

blk_t blk;

{

? ? ? ? id __strong array = [[NSMutableArray alloc] init];

? ? ? ? ?blk = &_main_block_impl_0(__main_block_func_0, &_main_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]);

發現 Block 用的結構體中附有 __strong 修飾符的成員變量。

C 語言結構體不能含有附有 __strong 修飾符的變量。因為編譯器不知道應核實進行 C 語言結構體的初始化和廢棄操作,不能很好的管理內存。

但是 Objective-C 的運行時庫能夠準確把握 Block 從棧復制到堆以及堆上 Block 被廢棄的時機,因此 Block 用結構體中即使含有附有 __strong 修飾符或 __weak 修飾符的變量,也可以恰當地進行初始化和廢棄。

為此需要使用在 __main_blockdesc0 結構體中增加的成員變量 copy 和 dispose,以及作為指針賦值給成員變量的 __main_block_cop\y0 函數和 __main_block_dispose0 函數

__main_block_copy_0 函數使用 _Block_object_assign 函數將對象類型對象賦值給 Block 用結構體的成員變量array 中并持有該對象。

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

}

_Block_object_assign 函數調用相當于 retain 實例方法的函數,將對象賦值在對象類型的結構體成員中。

__main_blockdispose0 函數使用 _Block_object_dispose 函數,釋放賦值在 Block 用結構體成員變量 array 中的對象。

static void __main_block_dispose_0(struct main_block_impl_0 *src)

{

? ? ? ?_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT)

}

_Block_object_dispose 函數調用相當于 release 實例方法的函數,釋放賦值在對象類型的結構體體成員變量中的對象。

在 Block 從棧復制到堆時(copy),以及堆上的 Block 被廢棄時(dispose)會調用這些函數。

將棧上的 Block 復制到堆上:

1.調用 Block 的 copy 實例方法時

2.Block 作為函數返回值返回時(ARC)

3.將 Block 賦值給附有 __strong 修飾符 id 類型的類或 Block 類型成員變量時

4.在方法命中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中傳遞 Block 時

在調用 Block 的 copy 實例方法時,如果 Block 配置在棧上,那么該 Block 會從棧復制到堆。

Block 作為函數返回值返回時,將 Block 賦值給附有 __strong 修飾符 id 類型的類或 Block 類型成員變量時,編譯器自動地將對象 Block 作為參數并調動 __Block_copy 函數。

在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中傳遞 Block時,在改方法或函數內部對傳遞過來的 Block 調用 Block 的 copy 實例方法或者 __Block_copy 函數。

static void __main_block_dispose_0(struct __main_block_impl_0 *src)

{

? ? ? ? _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);

}

通過BLOCKFIELDIS_OBJECT和BLOCKFIELDIS_BYREF參數,區分 copy 函數和 dispose 函數的對象類型是對象還是 __block 變量(copy 持有截獲的對象和所使用的 __block 變量,dispose 函數釋放所有使用的 __block 變量和截獲的對象)

ARC 無效

blk_t blk;

{

? ? ? ?id array = [[NSMutableArray alloc] init];

? ? ? ?blk = ^(id obj) {

? ? ? ? ? ? ? [arrray addObject:obj];

? ? ? ? ? ? ?NSLog(@"array count = %ld", [array count]);

? ? ? ? }; // 沒有調用 _Block_copy 函數,雖然截獲了對象,但并沒有持有對象,對象會隨著變量的作用域的結束而廢棄

? ? ? ? [array release];

}

blk([[NSObject alloc] init]);

blk([[NSObject alloc] init]);

blk([[NSObject alloc] init]);

代碼崩潰

因為只有調用 _Block_copy 函數才能持有截獲的附有 __strong 修飾符的對象類型的自動變量值。

因此,Block 中使用對象類型自動變量時,除以下情形外,推薦調用 Block 的 copy 實例方法。

1.Block 作為函數返回值返回時(ARC)

2.將 Block 賦值給附有 __strong 修飾符 id 類型的類或 Block 類型成員變量時

3.在方法命中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中傳遞 Block 時

__block 變量和對象

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

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

等同于:

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

clang 轉換后:

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

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;

}

static void __Block_byref_id_object_copy_131(void *dst, void *src) {

? ? ? _Block_object_assign((char *)dest + 40,*(void * *) ((char*)src + 40), 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,

? ? ? 0x2000000,

? ? ?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_assign 函數,持有 Block 截獲的對象。當堆上的 Block 被廢棄時,使用 _Block_object_dispose 函數,釋放 Block 截獲的對象。

在 __block 變量為附有 __strong 修飾符的 id 類型或對象類型自動變量的情形下會發生同樣的過程。當 __block 變量從棧復制到堆時,使用 _Block_object_assign 函數,持有賦值給 __block 變量的對象。

Block 循環引用

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

}

MyObject 類對象的 Block 類型成員變量 blk_ 持有賦值為 Block 的強引用。即 MyObject 類對象持有 Block。init 實例方法中執行的 Block 語法使用附有 __strong 修飾符的 id 類型變量 self。并且由于 Block 語法賦值在了成員變量 blk_ 中,因此通過 Block 語法生成在棧上的 Block 此時由棧復制到堆,并持有所使用的 self。self持有 Block,Block 持有 self。導致循環引用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容