參考文章
ibireme的博客文章
iOS 高級編程
本文屬於原創,轉載請註明出處
簡介
- Block截獲自動變量相當於截獲了變量的值,之後在Block外改變變量的值不會影響Block中的值
- Block中截獲的自動變量不能修改,修改會報錯,修改要給變量加__block修飾符,在Block調用後,原變量的值也會改變,但被__block修飾符修飾的變量已經不是單純的變量了
- 截獲並更改對象不會報錯,賦值會報錯,瞭解引用類型就知道原因,這裏不再贅述
Block的實質
int main()
{
void (^blk)(void) = ^ { printf("Block\n"); };
blk();
return 0;
}
通過以下規則
- 根據Block所屬的函數名(此處爲main),和該Block在該函數出現的順序值,此處爲0,來給Clang變換的函數命名
- 此處的__cself相當於self,爲指向當前Block的變量
以上代碼被轉爲
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 *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");
}
int main()
{
void (*blk)(void) = (void(*)void)&__main_block_impl_0(void*)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*) (struct __block_impl *))(struct __block_impl *) blk);
return 0;
}
幾個重要的結構體和函數
- __block_impl:爲Block的結構體,存放的isa指針指向當前對象,FuncPtr指向當前Block保存的實現函數
- __main_block_desc_0:保存了Block的狀態版本信息
- __main_block_func_0:保存了當前Block所實現的函數
- __main_block_impl_0:封裝了Block的結構體和裝有狀態信息的__main_block_desc_0,提供一個構造函數
以上生成和調用Block的過程可以被概述爲:
- 定義Block變量相當於調用__main_block_impl_0的構造函數,通過靜態函數指針__main_block_func_0和結構體指針__main_block_desc_0初始化
- 在構造函數內部,將傳遞進來的__main_block_func_0函數使用isa爲Block結構體中的成員變量FuncPtr初始化
- 調用Block時,使用isa指針取出Block中的FuncPtr函數進行調用
截獲自動變量
int main()
{
int val = 10;
void (^blk)(void) = ^ { printf("val"); };
blk()
}
該源碼中Block函數部分相當於
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
}
// __main_block_impl_0中的初始化如下
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
val = 10;
}
// 調用Block的函數時截獲變量相當於
static void __main_block_func_0(struct __main_block_impl_0 *cself)
{
int val = cself->val;
printf(val);
}
- 截獲變量時相當於執行Block語法時,Block語法所用的自動變量的值被保存到了Block的結構體實例裏
__block說明符
- __block的全稱爲:__block存儲域類說明符(__block storage-class-specifier)
struct __Block_byref_val_0 {
void *__isa;
__block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
// 沒寫出來的和上面相同
__Block_byref_val_0 *val;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
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_byref_val_0的結構體類型實例變量,並且被修飾的變量出現在該結構體裏
- __main_block_impl_0裏持有指向__block變量的__Block_byref_val_0結構體實例指針
- 通過__forwarding指針訪問該結構體裏的成員變量val
- 在這裏使用__block修飾的變量可以通過改變結構體指針改變該結構體成員變量的值
Block 的 Copy
- Block中的isa指針是指向其Class的,在Runtime中定義了以下幾種類:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count) { return rate * count; };
}
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);
}
// objc_retainBlock函數實際上就是__Block_copy函數
{
// 將棧上的Block複製到堆上
// 將堆上的地址作爲指針賦值給變量tmp
tmp = __Block_copy(tmp);
return objc_autoreleaseReturnValue(tmp);
}
- __Block變量會隨着Block變量被複制到堆上,在棧上的__Block變量被Block變量使用,複製到堆上後被Block變量持有,增加__Block變量的引用計數
{
__block int val = 0;
void (^blk)(void) = [^{ ++ val; }];
++ val;
blk();
}
// 無論是
^{ ++val; }
// 還是
++val;
// 都被轉爲
++ (val.__forwarding->val);
- 在變換Blcok語法的函數中,該變量val爲複製到堆上的__Block變量結構體實例,而用Block無關的變量val爲複製前棧上的__block結構體實例
- 但棧上的__block變量結構體實例在__block變量從棧複製到堆上時,會將成員變量__forwarding的值複製爲目標堆上的__block變量的結構體實例地址
- 所以無論__block結構體無論在Block語法外還是語法中使用(棧上或堆上),都能正確訪問
截獲對象
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id blk) {
[array addObject: obj];
NSLog(@"%ld", [array count]);
} copy];
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
};
// 相當於retain
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);
}
// 相當於release
static void _main_block_dispose(struct __main_block_impl_0 *src)
{
_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}
- Block從棧複製到堆上調用copy和dispose函數,
- 調用Block的copy實例方法時
- Blcok作爲函數返回值返回時
- 將Block賦值給__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時
Block的引用循環
使用 __weak 和 __Block可以解開引用循環,不過使用__Block會有限制