Block的實現

參考文章
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的過程可以被概述爲:

  1. 定義Block變量相當於調用__main_block_impl_0的構造函數,通過靜態函數指針__main_block_func_0和結構體指針__main_block_desc_0初始化
  2. 在構造函數內部,將傳遞進來的__main_block_func_0函數使用isa爲Block結構體中的成員變量FuncPtr初始化
  3. 調用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中定義了以下幾種類:
  1. _NSConcreteStackBlock
  2. _NSConcreteGlobalBlock
  3. _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函數,
  1. 調用Block的copy實例方法時
  2. Blcok作爲函數返回值返回時
  3. 將Block賦值給__strong修飾符id類型的類或Block類型成員變量時
  4. 在方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時
Block的引用循環

使用 __weak 和 __Block可以解開引用循環,不過使用__Block會有限制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章