第一篇文章 Block深層剖析(一)介紹了Block的一些基礎概念和用法。
第二篇文章 Block深層剖析(二)分析了最簡單的Block源碼。
這篇文章 將會介紹有關Block截獲的相關特點和源碼分析。
首先 總結一下Block截獲的特點:
(1)對于基本數據類型局部變量 截獲其值。
(2)對于對象類型的局部變量 連同其所有權修飾符一起截獲。
(3)以指針形式截獲局部靜態變量。
(4)不截獲全局變量和靜態全局變量。
1.截獲局部變量值
1.1查看源碼
#include "stdio.h"
int global_var = 110;
static int static_global_var = 119;
int main() {
int a = 0;
int val = 123;
static int static_var = 120;
__strong id strong_object = NULL;
void(^blockName)(void) = ^{
printf("局部 基本數據類型 變量 val = %d\n",val);
printf("局部 靜態變量 static_var = %d\n",static_var);
printf("全局變量 global_var = %d\n",global_var);
printf("全局靜態變量 static_global_var = %d\n",static_global_var);
printf("局部__strong對象類型變量 strong_object = %p\n",strong_object);
};
blockName();
return 0;
}
clang -rewrite-objc a.c 后得到的block源碼,省略與上一篇文章中分析過的源碼相同的 __block_impl結構體和__main_block_desc_0結構體。
int global_var = 110;
static int static_global_var = 119;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
int *static_var;
__strong id strong_object;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
int _val, int *_static_var, id _strong_object, int flags=0)
: val(_val), static_var(_static_var), strong_object(_strong_object) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// copy函數的調用時機:棧上的Block復制到堆上時
// 只有調用了copy函數才能持有截獲的附有__strong修飾符的對象類型的自動變量值,如果不調用即使截獲了對象,它也會隨著變量的作用域結束而被廢棄。
因此在Block中使用對象類型的自動變量時,需要調用Block的copy函數
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign(
(void*)&dst->strong_obj, (void*)src->strong_obj, 3
/*BLOCK_FIELD_IS_OBJECT*/);
}
// dispose函數調用時機:堆上的Block被廢棄時
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->strong_obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val;
int *static_var = __cself->static_var;
id strong_object = __cself->strong_object;
printf("局部 基本數據類型 變量 val = %d\n",val);
printf("局部 靜態變量 static_var = %d\n",(*static_var));
printf("全局變量 global_var = %d\n",global_var);
printf("全局靜態變量 static_global_var = %d\n",static_global_var);
printf("局部__strong對象類型變量 strong_object = %p\n",strong_object);
}
int main() {
int a = 0;
int val = 123;
static int static_var = 120;
__attribute__((objc_ownership(strong))) id strong_object = __null;
void(*blockName)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA,
val, &static_var, strong_object, 570425344));
((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
return 0;
}
1.2源碼分析
對比與之前的不同之處發現:
(1)Block語法表達式中使用的局部變量val ,作為成員變量添加到了__main_block_impl_0結構體中。
(2)局部靜態變量static_var,以指針類型的成員變量添加到__main_block_impl_0結構體中。
(3)對象類型的局部變量strong_obj,連同其所有權修飾符一起截獲作為成員變量添加到__main_block_impl_0結構體中。
copy函數的調用時機:棧上的Block復制到堆上時
只有調用了copy函數才能持有截獲的附有__strong修飾符的對象類型的自動變量值,如果不調用即使截獲了對象,它也會隨著變量的作用域結束而被廢棄。
因此在Block中使用對象類型的自動變量時,需要調用Block的copy函數。
dispose函數調用時機:堆上的Block被廢棄時。
(4)全局變量global_var和全局靜態變量static_global_var沒有作為成員變量添加到__main_block_impl_0結構體中。
這里注意在Block語法表達式中沒有使用的局部變量a,是不會添加到__main_block_impl_0結構體中的
總結:“截獲自動變量值” 意味著在執行Block語法時,Block語法表達式中使用的自動變量被保存到Block的結構體實例中。
2.__block說明符
__block修飾符是為了解決在Block語法中不能修改截獲變量問題(注意"使用"和"賦值"是不一樣的)。__block說明符全稱:"__block存儲域說明符"。
存儲域說明符用于指定將變量值設置到哪個存儲域中。
如auto表示作為自動變量存儲在棧中;
static表示作為靜態變量存儲在數據區中。
下面結合源碼進行分析
2.1查看源碼
#include "stdio.h"
int main()
{
__block int val = 123;
void(^blockName)(void) = ^{
val = 456;
printf("局部變量 val = %d\n",val);
};
blockName();
return 0;
}
clang -rewrite-objc a.c 后得到的block源碼。
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) = 456;
printf("局部變量 val = %d\n",(val->__forwarding->val));
}
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()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,(__Block_byref_val_0 *)&val
, 0, sizeof(__Block_byref_val_0), 123};
void(*blockName)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))
((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
return 0;
}
2.2源碼分析
2.2.1 __block變量val
__block int val = 123;
clang后源碼
__Block_byref_val_0 val = {
(void*)0,(__Block_byref_val_0 *)&val
, 0, sizeof(__Block_byref_val_0), 123};
我們發現__block變量val 變成了__Block_byref_val_0結構體類型的自動變量。
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
查看__Block_byref_val_0結構體聲明,發現val變量出現在改結構體中,這就意味著
__Block_byref_val_0結構體持有相當于原自動變量的成員變量。
2.2.2 __block變量賦值
^{
val = 456;
printf("局部變量 val = %d\n",val);
};
clang后源碼
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) = 456;
printf("局部變量 val = %d\n",(val->__forwarding->val));
}
- Block的__main_block_impl_0結構體實例 持有指向__block變量的__Block_byref_val_0結構體實例的指針。
- __Block_byref_val_0結構體實例的成員變量__forwarding 持有指向該實例自身的指針。
通過成員變量__forwarding可以訪問到成員變量val,上面說過這個成員變量val相當于原自動變量。
2.2.3 __block變量的捕獲
Block的__main_block_impl_0結構體實例 持有指向__block變量的__Block_byref_val_0結構體實例的指針。
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;
}
};