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語法表達式使用fmt和val。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。導致循環引用