1 Block機制
(Very Good) Block技巧與底層解析
http://www.lxweimin.com/p/51d04b7639f1
(Good)談Objective-CBlock的實現
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
1.1 概念簡介
????????閉包是一個函數(或指向函數的指針),再加上該函數執行的外部的上下文變量(有時候也稱作自由變量)。block實際上就是Objective-C語言對于閉包的實現。
????????Block具有將臨時函數體創建為表達式的優勢。Apple文檔中指出:
????Block是符合如下要求的匿名內聯的代碼集:
????1、和函數一樣具有一個指定類型的參數列表;
????2、有一個可以推導或聲明的返回值類型;
????3、可以從它被定義的詞義范圍中捕捉狀態;
????4、可以在需要的時候改變詞義范圍的狀態;
????5、可以和相同的詞義范圍中定義的其他的Block共享更改的可能。
????6、可以在詞義范圍(堆棧幀)被銷毀后繼續共享和修改該詞義范圍(堆棧幀)的狀態。
????????Block是一個自包含的小代碼段,封裝了用于遍歷(線性遍歷)或者回調,可以并發執行的任務單元。代碼塊本質上是和其他變量類似。不同的是,代碼塊存儲的數據是一個函數體。使用代碼塊是,你可以像調用其他標準函數一樣,傳入參數數,并得到返回值。
????????脫字符(^)是塊的語法標記。按照我們熟悉的參數語法規約所定義的返回值以及塊的主體(也就是可以執行的代碼)。下圖是如何把塊變量賦值給一個變量的語法講解:
????????按照調用函數的方式調用塊對象變量就可以了:
int result = myBlock(4); //result是28
1.2 研究工具:clang
????????為了研究編譯器是如何實現 block 的,我們需要使用 clang。clang 提供一個命令,可以將 Objetive-C 的源碼改寫成 c 語言的,借此可以研究 block 具體的源碼實現方式。該命令是:
????clang-rewrite-objc block.c
1.3 block內存實現機制
1.3.1 block編譯轉換結構
????????首先,看一個極簡的block:
int main(int argc, const char* argv[]) {
??? @autoreleasepool {
??????? ^{ };
??? }
??? return 0;
}
block編譯轉換結構
對其執行clang -rewrite-objc編譯轉換成C++實現,得到以下代碼:
//block類型實質
struct__block_impl {
??? void *isa;
??? int Flags;
??? int Reserved;
??? void *FuncPtr;
};
//Block的實現本質
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) {
}
static struct__main_block_desc_0 {
? size_t reserved;
? size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct__main_block_impl_0)};
int main(int argc, const char* argv[]) {
????/* @autoreleasepool */
????{
??? ????__AtAutoreleasePool __autoreleasepool;
??????? (void (*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA);
??? }
??? return 0;
}
????????不難看出其中的__main_block_impl_0就是block的一個C++的實現(最后面的_0代表是main中的第幾個block),也就是說也是一個結構體。
????其中__block_impl的定義如下:
struct__block_impl {
? void *isa;
? int Flags;
? int Reserved;
? void *FuncPtr;
};
其結構體成員如下:
????1、isa,指向所屬類的指針,也就是block的類型
????2、flags,標志變量,在實現block的內部操作時會用到
????3、Reserved,保留變量
????4、FuncPtr,block執行時調用的函數指針
????5、可以看出,它包含了isa指針(包含isa指針的皆為對象),也就是說block也是一個對象(runtime里面,對象和類都是用結構體表示)。
__main_block_desc_0的定義如下:
static struct__main_block_desc_0 {
? size_t reserved;
? size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
????????其結構成員含義如下:
????1、reserved:保留字段
????2、Block_size:block大小(sizeof(struct __main_block_impl_0))
????????以上代碼在定義__main_block_desc_0結構體時,同時創建了__main_block_desc_0_DATA,并給它賦值,以供在main函數中對__main_block_impl_0進行初始化。
????????__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;
? }
????????可以看出,
????1、__main_block_impl_0的isa指針指向了_NSConcreteStackBlock,
????2、從main函數中看, __main_block_impl_0的FuncPtr指向了函數__main_block_func_0
????3、__main_block_impl_0的Desc也指向了定義__main_block_desc_0時就創建的__main_block_desc_0_DATA,其中紀錄了block結構體大小等信息。
????????以上就是根據編譯轉換的結果,對一個簡單block的解析,后面會將block操作不同類型的外部變量,對block結構的影響進行相應的說明。
1.3.2 block實際結構
????????接下來觀察下Block_private.h文件中對block的相關結構體的真實定義:
/* Revised new layout. */
struct Block_descriptor {
??? unsigned long intreserved;
??? unsigned long intsize;
??? void (*copy)(void *dst, void*src);
??? void (*dispose)(void*);
};
struct Block_layout {
??? void *isa;
??? int flags;
??? int reserved;
??? void (*invoke)(void*, ...);
??? struct Block_descriptor *descriptor;
??? /* Imported variables. */
};
???????一個 block 實例實際上由 6 部分構成:
????1、isa 指針,所有對象都有該指針,用于實現對象相關的功能。
????2、flags,用于按 bit 位表示一些 block 的附加信息,本文后面介紹 block copy 的實現代碼可以看到對該變量的使用。
????3、reserved,保留變量。
????4、invoke,函數指針,同上文的FuncPtr,block執行時調用的函數指針,block定義時內部的執行代碼都在這個函數中。
????5、descriptor, 表示該 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針。
????6、variables,capture 過來的變量,block 能夠訪問它外部的局部變量,就是因為將這些變量(或變量的地址)復制到了結構體中。
? ? 7、Block_descriptor,block的詳細描述。
? ? 8、copy/dispose,輔助拷貝/銷毀函數,處理block范圍外的變量時使用
????????總體來說,block就是一個里面存儲了指向函數體中包含定義block時的代碼塊的函數指針,以及block外部上下文變量等信息的結構體。
1.4 block的類型
????????在 Objective-C 語言中,一共有 3 種類型的 block:
????1、_NSConcreteGlobalBlock 全局的靜態 block,不會訪問任何外部變量。
????2、_NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀。
????3、_NSConcreteMallocBlock 保存在堆中的 block,當引用計數為 0 時會被銷毀。
????????其中前2種在Block.h種聲明,后1種在Block_private.h中聲明,所以最后1種基本不會在源碼中出現。
1.4.1 NSConcreteGlobalBlock
????????首先,根據前面兩種類型,編寫以下代碼:
void(^globalBlock)() = ^{
};
int main(int argc, const char* argv[]) {
??? @autoreleasepool {
??????? void(^stackBlock1)() = ^{
??????? };
??? }
??? return 0;
}
????????對其進行編譯轉換后得到以下縮略代碼:
// globalBlock
struct__globalBlock_block_impl_0 {
? struct__block_impl impl;
? struct__globalBlock_block_desc_0* Desc;
? __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
??? impl.isa = &_NSConcreteGlobalBlock;
??? impl.Flags = flags;
??? impl.FuncPtr = fp;
??? Desc = desc;
? }
};
...
1.4.2 NSConcreteStackBlock
// stackBlock
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;
? }
};
...
int main(int argc, const char* argv[]) {
??? /* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool;
??????? void (*stackBlock)() = (void (*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA);
??? }
??? return 0;
}
????????可以看出globalBlock的isa指向了_NSConcreteGlobalBlock,即在全局區域創建,編譯時具體的代碼就已經確定在上圖中的代碼段中了,block變量存儲在全局數據存儲區;stackBlock的isa指向了_NSConcreteStackBlock,即在棧區創建。
1.4.3 NSConcreteMallocBlock
????????接下來是在堆中的block,堆中的block無法直接創建,其需要由_NSConcreteStackBlock類型的block拷貝而來(也就是說block需要執行copy之后才能存放到堆中)。由于block的拷貝最終都會調用_Block_copy_internal函數,所以觀察這個函數就可以知道堆中block是如何被創建的了:
static void*_Block_copy_internal(const void *arg, constintflags) {
??? struct Block_layout *aBlock;
??? ...
??? aBlock = (struct Block_layout*)arg;
??? ...
??? // Its a stack block.?Make a copy.
??? if(!isGC){
??????? // 申請block的堆內存
??????? struct Block_layout *result = malloc(aBlock->descriptor->size);
??????? if (!result) return (void *)0;
??????? // 拷貝棧中block到剛申請的堆內存中
??????? memmove(result, aBlock,aBlock->descriptor->size);// bitcopy first
??????? // reset refcount
??????? result->flags &= ~(BLOCK_REFCOUNT_MASK);??? // XXX not needed
??????? result->flags |= BLOCK_NEEDS_FREE |1;
??????? // 改變isa指向_NSConcreteMallocBlock,即堆block類型
??????? result->isa = _NSConcreteMallocBlock;
??????? if(result->flags & BLOCK_HAS_COPY_DISPOSE) {
??????????? //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
???????????(*aBlock->descriptor->copy)(result, aBlock);// do fixup
??????? }
??????? return result;
??? }
??? else{
??????? ...
??? }
}
????????從以上代碼以及注釋可以很清楚的看出,函數通過memmove將棧中的block的內容拷貝到了堆中,并使isa指向了_NSConcreteMallocBlock。
????????block主要的一些學問就出在棧中block向堆中block的轉移過程中了。
1.4.4 不同類型block的復制
????????block的復制代碼在_Block_copy_internal函數中。
1.4.4.1 棧block的復制
????????從以下代碼可以看出,棧block的復制不僅僅復制了其內容,還添加了一些額外的東西:
????1、往flags中并入了BLOCK_NEEDS_FREE(這個標志表明block需要釋放,在release以及再次拷貝時會用到);
????2、如果有輔助copy函數(BLOCK_HAS_COPY_DISPOSE),那么就調用(這個輔助copy函數是用來拷貝block捕獲的變量的);
...
struct Block_layout *result = malloc(aBlock->descriptor->size);
if(!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size);// bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK);??? // XXX not needed
result->flags |= BLOCK_NEEDS_FREE |1;
result->isa = _NSConcreteMallocBlock;//復制時轉移到了堆上
if(result->flags & BLOCK_HAS_COPY_DISPOSE) {
?????? //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
??????(*aBlock->descriptor->copy)(result, aBlock);// do fixup
?? }
?? return result;
...
1.4.4.2 堆block的復制
????????從以下代碼看出,如果block的flags中有BLOCK_NEEDS_FREE標志(block從棧中拷貝到堆時添加的標志),就執行latching_incr_int操作,其功能就是讓block的引用計數加1。所以堆中block的拷貝只是單純地改變了引用計數。
? ...
? if(aBlock->flags & BLOCK_NEEDS_FREE) {
??????? // latches on high
???????latching_incr_int(&aBlock->flags);
??????? return aBlock;
??? }
? ...
1.4.4.3 全局block的復制
????????從以下代碼看出,對于全局block,函數沒有做任何操作,直接返回了傳入的block。
? ...
? else if(aBlock->flags & BLOCK_IS_GLOBAL) {
??????? return aBlock;
??? }
? ...
1.5 捕捉變量對block結構的影響
????????接下來會編譯轉換捕捉不同變量類型的block,以對比它們的區別。
1.5.1 局部變量
前:
- (void)test
{
??? int a;
??? ^{a;};
}
后:
struct__Person__test_block_impl_0 {
? struct__block_impl impl;
? struct__Person__test_block_desc_0* Desc;
? int a;
? // a(_a)是構造函數的參數列表初始化形式,相當于a = _a。從_I_Person_test看,傳入的就是a
? __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0): a(_a){
??? impl.isa = &_NSConcreteStackBlock;
??? impl.Flags = flags;
??? impl.FuncPtr = fp;
??? Desc = desc;
? }
};
static void __Person__test_block_func_0(struct__Person__test_block_impl_0 *__cself) {
? ? int a = __cself->a; // bound by copy
? ? a;
}
static struct__Person__test_block_desc_0 {
? size_treserved;
? size_tBlock_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct__Person__test_block_impl_0)};
static void_I_Person_test(Person * self, SEL _cmd) {
??? int a;
??? (void(*)())&__Person__test_block_impl_0((void*)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);
}
????????可以看到,block相對于文章開頭增加了一個int類型的成員變量,他就是用來存儲外部變量a的。可以看出,這次拷貝只是一次值傳遞。并且當我們想在block中進行以下操作時,將會發生錯誤
^{a = 10;};
????????因為_I_Person_test函數中的a和Persontest_block_func_0函數中的a并沒有在同一個作用域,所以在block對a進行賦值是沒有意義的,所以編譯器給出了錯誤。我們可以通過地址傳遞來消除以上錯誤:
- (void)test
{
??? int a = 0;
??? // 利用指針p存儲a的地址
??? int *p = &a;
??? ^{
??????? ????// 通過a的地址設置a的值
??????? ????*p = 10;
??? };
}
????????但是變量a的生命周期是和方法test的棧相關聯的,當test運行結束,棧隨之銷毀,那么變量a就會被銷毀,p也就成為了野指針。如果block是作為參數或者返回值,這些類型都是跨棧的,也就是說再次調用會造成野指針錯誤。
1.5.2 全局變量
前:
// 全局靜態
static int a;
// 全局
int b;
- (void)test
{
??? ^{
??????? a =10;
??????? b =10;
??? };
}
后:
static int a;
int b;
struct__Person__test_block_impl_0 {
? struct__block_impl impl;
? struct__Person__test_block_desc_0* Desc;
? __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int flags=0) {
??? impl.isa = &_NSConcreteStackBlock;
??? impl.Flags = flags;
??? impl.FuncPtr = fp;
??? Desc = desc;
? }
};
static void __Person__test_block_func_0(struct__Person__test_block_impl_0 *__cself) {
??????? a = 10;
??????? b =10;
??? }
static struct__Person__test_block_desc_0 {
? size_t reserved;
? size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct__Person__test_block_impl_0)};
static void_I_Person_test(Person * self, SEL _cmd) {
??? ????(void(*)())&__Person__test_block_impl_0((void*)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);
}
????????可以看出,因為全局變量都是在靜態數據存儲區,在程序結束前不會被銷毀,所以block直接訪問了對應的變量,而沒有在Persontest_block_impl_0結構體中給變量預留位置。
1.5.3 局部靜態變量
前
- (void)test
{
??? static int a;
??? ^{
??????? a =10;
??? };
}
后:
struct__Person__test_block_impl_0 {
? ? struct__block_impl impl;
? ? struct__Person__test_block_desc_0* Desc;
? ? int *a;
? ? __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
??? ????impl.isa =&_NSConcreteStackBlock;
??? ????impl.Flags = flags;
??? ????impl.FuncPtr = fp;
??? ????Desc = desc;
? ? }
};
static void __Person__test_block_func_0(struct__Person__test_block_impl_0 *__cself) {
? ? int *a = __cself->a; // bound by copy
? ? // 這里通過局部靜態變量a的地址來對其進行修改
? ? (*a) =10;
}
static struct__Person__test_block_desc_0 {
? ? size_t reserved;
? ? size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct__Person__test_block_impl_0)};
static void_I_Person_test(Person * self, SEL _cmd) {
? ? ? static int a;
? ? ? // 傳入a的地址
? ? ? (void(*)())&__Person__test_block_impl_0((void*)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);
}
????????需要注意一點的是靜態局部變量是存儲在靜態數據存儲區域的,也就是和程序擁有一樣的生命周期,也就是說在程序運行時,都能夠保證block訪問到一個有效的變量。但是其作用范圍還是局限于定義它的函數中,所以只能在block通過靜態局部變量的地址來進行訪問。
????????關于變量的存儲我原來的這篇博客有提及:c語言臆想--全局---局部變量
1.5.4 __block修飾的變量
前:
- (void)test
{
?? __block int a;
??? ^{
??????? a =10;
??? };
}
后:
struct __Block_byref_a_0{
? void *__isa;
? __Block_byref_a_0 *__forwarding;
? ?int __flags;
? ?int __size;
? ?int a;
};
struct__Person__test_block_impl_0 {
? ? struct__block_impl impl;
? ? struct__Person__test_block_desc_0* Desc;
? ? __Block_byref_a_0 *a;// by ref
? ? __Person__test_block_impl_0(void *fp, struct__Person__test_block_desc_0 *desc,__Block_byref_a_0 *_a,int flags=0) :a(_a->__forwarding){
? ? ? impl.isa = &_NSConcreteStackBlock;
? ? ? impl.Flags = flags;
? ? ? impl.FuncPtr = fp;
? ? ? Desc = desc;
? }
};
static void __Person__test_block_func_0(struct__Person__test_block_impl_0 *__cself) {
? ? __Block_byref_a_0 *a = __cself->a; //? bound by ref
? ? // 注意,這里的_forwarding用來保證操作的始終是堆中的拷貝a,而不是棧中的a
? ? (a->__forwarding->a) =10;
}
static void __Person__test_block_copy_0(struct__Person__test_block_impl_0*dst, struct__Person__test_block_impl_0*src) {
????_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __Person__test_block_dispose_0(struct__Person__test_block_impl_0*src) {
????_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct__Person__test_block_desc_0 {
? ? size_t reserved;
? ? size_t Block_size;
? ? void(*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
? ? void(*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct__Person__test_block_impl_0), __Person__test_block_copy_0,? __Person__test_block_dispose_0};
static void_I_Person_test(Person * self, SEL _cmd) {
? ? ? // __block將a包裝成了一個對象
? ? ?__attribute__((__blocks__(byref)))__Block_byref_a_0 a = {??
????????(void*)0,(__Block_byref_a_0*)&a, 0, sizeof(__Block_byref_a_0)};
??? ????(void(*)())&__Person__test_block_impl_0((void*)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}
????????可以看到,對比上面的結果,明顯多了__Block_byref_a_0結構體,這個結構體中含有isa指針,所以也是一個對象,它是用來包裝局部變量a的。當block被copy到堆中時,__Person__test_block_impl_0的拷貝輔助函數__Person__test_block_copy_0會將__Block_byref_a_0拷貝至堆中,所以即使局部變量所在堆被銷毀,block依然能對堆中的局部變量進行操作。其中__Block_byref_a_0成員指針__forwarding用來指向它在堆中的拷貝,其依據源碼如下:
static void _Block_byref_assign_copy(void *dest, const void *arg, const intflags) {
? ? ? struct Block_byref **destp = (structBlock_byref **)dest;
? ? ? struct Block_byref *src = (structBlock_byref *)arg;
??? ...
??? // 堆中拷貝的forwarding指向它自己
??? copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
??? // 棧中的forwarding指向堆中的拷貝
??? src->forwarding = copy;? // patch stack to point to heap copy
??? ...
}
????????這樣做是為了保證操作的值始終是堆中的拷貝,而不是棧中的值。(處理在局部變量所在棧還沒銷毀,就調用block來改變局部變量值的情況,如果沒有__forwarding指針,則修改無效)。至于block如何實現對局部變量的拷貝,下面會詳細說明。
1.5.5 self隱式循環引用
前:
@implementation Person
{
? ? ? int _a;
? ? ? void (^_block)();
}
- (void)test
{
? ? void(^_block)() = ^{
??????? _a =10;
??? };
}
@end
后:
struct __Person__test_block_impl_0 {
? ? struct __block_impl impl;
? ? struct __Person__test_block_desc_0* Desc;
? ? // 可以看到,block強引用了self
? ? Person *self;
? ? __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person
*_self, int flags=0) : self(_self) {
??? ????impl.isa = &_NSConcreteStackBlock;
??? ????impl.Flags = flags;
??? ????impl.FuncPtr = fp;
??? ????Desc = desc;
? ? }
};
static void __Person__test_block_func_0(struct__Person__test_block_impl_0 *__cself) {
? ? Person *self = __cself->self; // bound by copy
? ? (*(int*)((char *)self +OBJC_IVAR_$_Person$_a)) = 10;
}
static void __Person__test_block_copy_0(struct__Person__test_block_impl_0*dst, struct__Person__test_block_impl_0*src) {
? ??_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __Person__test_block_dispose_0(struct__Person__test_block_impl_0*src) {
????_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct__Person__test_block_desc_0 {
? ? size_t reserved;
? ? size_t Block_size;
? ? void (*copy)(struct __Person__test_block_impl_0*, struct__Person__test_block_impl_0*);
? ? void(*dispose)(struct__Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct__Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
static void _I_Person_test(Person * self, SEL _cmd) {
? ????void(*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);
}
????????如果在編譯轉換前,將_a改成self.a,能很明顯地看出是產生了循環引用(self強引用block,block強引用self)。那么使用_a呢?經過編譯轉換后,依然可以在__Person__test_block_impl_0看見self的身影。且在函數_I_Person_test中,傳入的參數也是self。通過以下語句,可以看出,不管是用什么形式訪問實例變量,最終都會轉換成self+變量內存偏移的形式。所以在上面例子中使用_a也會造成循環引用。
static void __Person__test_block_func_0(struct__Person__test_block_impl_0 *__cself) {
? ? Person *self = __cself->self; // bound by copy
? ? // self+實例變量a的偏移值
? ? (*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
}
1.6 block輔助函數
????????在捕獲變量為__block修飾的基本類型,或者為對象時,block才會有這兩個輔助函數。
1.6.1 輔助copy函數
????????在捕獲變量為__block修飾的基本類型,或者為對象時,block才會有這兩個輔助函數。
????????block捕捉變量拷貝函數為_Block_object_assign。在調用復制block的函數_Block_copy_internal時,會根據block有無輔助函數來對捕捉變量拷貝函數_Block_object_assign進行調用。而在_Block_object_assign函數中,也會判斷捕捉變量包裝而成的對象(Block_byref結構體)是否有輔助函數,來進行調用。
__block修飾的基本類型的輔助函數
編寫以下代碼:
typedef void(^Block)();
int main(int argc, const char* argv[]) {
??? @autoreleasepool{
? ? ? ? ? __block int a;? ?
? ? ? ? ? Block block = ^ {
? ? ? ? ? ? ? a;
? ? ? ? ? };
}
轉換成C++代碼后:
typedef void(*Block)();
// __block int a 語句實現的本質
struct __Block_byref_a_0 {
? ? void *__isa;
? __Block_byref_a_0 *__forwarding;
? ?int __flags;
? ?int __size;
? ?int a;
};
// block本質實現
struct __main_block_impl_0 {
? ?struct __block_impl impl;
? ? struct __main_block_desc_0* Desc;
? ? __Block_byref_a_0 *a;// by ref
? ? __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) :a(_a->__forwarding) {
??? ????impl.isa = &_NSConcreteStackBlock;
??? ????impl.Flags = flags;
??????? impl.FuncPtr = fp;
??? ????Desc = desc;
? ? }
};
// block函數體
static void __main_block_func_0(struct__main_block_impl_0 *__cself) {
? ? __Block_byref_a_0 *a =__cself->a;// bound by ref?
? ? (a->__forwarding->a);
}
// 輔助copy函數
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct__main_block_impl_0*src)
{
? ? ? _Block_object_assign((void*)&dst->a, (void*)src->a, 8 /*BLOCK_FIELD_IS_BYREF*/);
}
// 輔助dispose函數
static void __main_block_dispose_0(struct__main_block_impl_0*src)
{
????_Block_object_dispose((void*)src->a,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(int argc, const char* argv[])
{
????/* @autoreleasepool */
??? {
??? ??????__AtAutoreleasePool __autoreleasepool;????
??? ??????// 這里創建了,并將a的flags設置為0
? ????????__attribute__((__blocks__(byref)))__Block_byref_a_0 a =
? ????????{(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
? ? ? ? ? Block block = (void (*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0*)&a, 570425344);
? }
??? return 0;
}
????????從上面代碼中,被__block修飾的a變量變為了__Block_byref_a_0類型,根據這個格式,從源碼中查看得到相似的定義:
struct Block_byref {
? ? ? void *isa;
? ? ? struct Block_byref *forwarding;
? ? ? int flags; /* refcount; */
? ? ? int size;
? ? ? void(*byref_keep)(struct Block_byref *dst, structBlock_byref *src);
? ? ? void(*byref_destroy)(structBlock_byref *);
? ? ? /* long shared[0]; */
};
// 做下對比
struct__Block_byref_a_0 {
? ??void *__isa;
??? __Block_byref_a_0 *__forwarding;
???int __flags;
???int __size;
???int a;
};
// flags/_flags類型
enum{
??????? /* See function implementation for a more complete description of these fields and combinations */
??????? // 是一個對象
??????? BLOCK_FIELD_IS_OBJECT =? 3,? /* id, NSObject, __attribute__((NSObject)), block, ... */
??????? // 是一個block
??????? BLOCK_FIELD_IS_BLOCK? =? 7,? /* a block variable */
??????? // 被__block修飾的變量
??????? BLOCK_FIELD_IS_BYREF =? 8,? /* the on stack structure holding the __block variable */
??????? // 被__weak修飾的變量,只能被輔助copy函數使用
??????? BLOCK_FIELD_IS_WEAK? =16,? /* declared __weak, only used in byref copy helpers */
??????? // block輔助函數調用(告訴內部實現不要進行retain或者copy)
??????? BLOCK_BYREF_CALLER? =128? /* called from __block (byref) copy/dispose support routines. */
??? };
??? ????可以看出,__block將原來的基本類型包裝成了對象。因為以上兩個結構體的前4個成員的類型都是一樣的,內存空間排列一致,所以可以進行以下操作:
// 轉換成C++代碼
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct__main_block_impl_0*src)
{
????????_Block_object_assign((void*)&dst->a, (void*)src->a, 8 /*BLOCK_FIELD_IS_BYREF*/);
}
// _Block_object_assign源碼
void_Block_object_assign(void *destAddr, const void *object, const intflags) {
??? ...
??? else if((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)? {
??????? // copying a __block reference from the stack Block to the heap
??????? // flags will indicate if it holds a __weak reference and needs a special isa
??????? _Block_byref_assign_copy(destAddr, object, flags);
??? }
??? ...
}
// _Block_byref_assign_copy源碼
static void _Block_byref_assign_copy(void *dest, const void *arg, const intflags)
{
??? ????// 這里因為前面4個成員的內存分布一樣,所以直接轉換后,使用Block_byref的成員變量名,能訪問到__Block_byref_a_0的前面4個成員
??? ????structBlock_byref **destp = (structBlock_byref **)dest;
??? ????structBlock_byref *src = (structBlock_byref *)arg;
??? ...
????else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0)
????{
??????? ????// 從main函數對__Block_byref_a_0的初始化,可以看到初始化時將flags賦值為0
??????? ????// 這里表示第一次拷貝,會進行復制操作(此處是淺拷貝還是深拷貝?從實際情況來看是前拷貝),并修改原來flags的值
??????? ????// static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;
??????? ????// 可以看出,復制后,會并入BLOCK_NEEDS_FREE,后面的2是block的初始引用計數
??????? ????...
??????? ????copy->flags = src->flags | _Byref_flag_initial_value;
??????? ????...????
??? }
??? // 已經拷貝到堆了,只增加引用計數
????else if((src->forwarding->flags &BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE)
????{
? ? ? ? ? latching_incr_int(&src->forwarding->flags);
??? }
??? // 普通的賦值,里面最底層就*destptr = value;這句表達式
???_Block_assign(src->forwarding, (void**)destp);
}
??? 主要操作都在代碼注釋中了,總體來說,__block修飾的基本類型會被包裝為對象,并且只在最初block拷貝時復制一次,后面的拷貝只會增加這個捕獲變量的引用計數。
1.6.2 沒有__block修飾
typedef void(^Block)();
int main(int argc, const char* argv[]) {
??? @autoreleasepool{
? ? ? ? ? NSObject *a = [[NSObjectalloc] init];
? ? ? ? ? Block block = ^ {
? ? ? ? ? ? ? a;
? ? ? ? ? };
??? }
??? return 0;
}
????????首先,在沒有__block修飾時,對象編譯轉換的結果如下,刪除了一些變化不大的代碼:
static void __main_block_func_0(struct__main_block_impl_0 *__cself)
{
? ? ? NSObject *a = __cself->a;// bound by copy
? ? ? a;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct__main_block_impl_0*src)
{
????_Block_object_assign((void*)&dst->a, (void*)src->a, 3 /*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct__main_block_impl_0*src)
{
????_Block_object_dispose((void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
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),
??? ????對象在沒有__block修飾時,并沒有產生__Block_byref_a_0結構體,只是將標志位修改為BLOCK_FIELD_IS_OBJECT。而在_Block_object_assign中對應的判斷分支代碼如下:
...
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
??? _Block_retain_object(object);
??? _Block_assign((void *)object, destAddr);
}
...
??? 可以看到,block復制時,會retain捕捉對象,以增加其引用計數。
1.6.3 有__block修飾
typedef void(^Block)();
int main(int argc, const char* argv[]) {
??? @autoreleasepool{
? ? ? ? ? __blockNSObject *a = [[NSObjectalloc] init];
??????? Block block = ^ {
??????????? a;
??????? };
??? }
??? return 0;
}
??? 在這種情況下,編譯轉換的部分結果如下:
struct__Block_byref_a_0
{
? ??void *__isa;
??? __Block_byref_a_0 *__forwarding;
???int __flags;
???int __size;
???void (*__Block_byref_id_object_copy)(void*, void*);
???void (*__Block_byref_id_object_dispose)(void*);
???NSObject *a;
};
int main(int argc, const char* argv[])
{
????/* @autoreleasepool */
????{
? ????????__AtAutoreleasePool __autoreleasepool;
????????attribute__((__blocks__(byref)))__Block_byref_a_0 a =? {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....
????};
????? Block block = (void (*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0*)&a, 570425344);
??? }
??? //以下的40表示__Block_byref_a_0對象a的位移(4個指針(32字節)+2個int變量(8字節)=40字節)
static void __Block_byref_id_object_copy_131(void *dst, void*src)
{
?????? _Block_object_assign((char*)dst + 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_byref_a_0另外增加了兩個輔助函數__Block_byref_id_object_copy、__Block_byref_id_object_dispose,以實現對對象內存的管理。其中兩者的最后一個參數131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT,BLOCK_BYREF_CALLER表示在內部實現中不對a對象進行retain或copy;以下為相關源碼:
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER)
{
??? ...
??? else {
??????? //do *not* retain or *copy*__block variables whatever they are
??????? _Block_assign((void *)object, destAddr);
??? }
}
??? _Block_byref_assign_copy函數的以下代碼會對上面的輔助函數(__Block_byref_id_object_copy_131)進行調用;570425344表示BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR,所以會執行以下相關源碼:
if (src->flags& BLOCK_HAS_COPY_DISPOSE)
{
??? // Trust copy helper to copy everything of interest
??? // If more than one field shows up in a byref block this is wrong XXX
??? copy->byref_keep =src->byref_keep;
??? copy->byref_destroy =src->byref_destroy;
??? (*src->byref_keep)(copy,src);
}
1.7 ARC中block的工作
????????蘋果文檔提及,在ARC模式下,在棧間傳遞block時,不需要手動copy棧中的block,即可讓block正常工作。主要原因是ARC對棧中的block自動執行了copy,將_NSConcreteStackBlock類型的block轉換成了_NSConcreteMallocBlock的block。
1.7.1 block試驗
????????下面對block做點實驗:
int main(int argc, const char* argv[]) {
??? @autoreleasepool{
??????? int i = 10;
??????? void(^block)() = ^{i;};
??????? __weak void(^weakBlock)() = ^{i;};
??????? void(^stackBlock)() = ^{};
??????? // ARC情況下
??????? // 創建時,都會在棧中
??????? // <__NSStackBlock__: 0x7fff5fbff730>
??????? NSLog(@"%@", ^{i;});
??????? // 因為stackBlock為strong類型,且捕獲了外部變量,所以賦值時,自動進行了copy
??????? // <__NSMallocBlock__: 0x100206920>
??????? NSLog(@"%@", block);
??????? // 如果是weak類型的block,依然不會自動進行copy
??????? // <__NSStackBlock__: 0x7fff5fbff728>
??????? NSLog(@"%@", weakBlock);
??????? // 如果block是strong類型,并且沒有捕獲外部變量,那么就會轉換成__NSGlobalBlock__
??????? // <__NSGlobalBlock__: 0x100001110>
??????? NSLog(@"%@", stackBlock);
??????? // 在非ARC情況下,產生以下輸出
??????? // <__NSStackBlock__: 0x7fff5fbff6d0>
??????? // <__NSStackBlock__: 0x7fff5fbff730>
??????? // <__NSStackBlock__: 0x7fff5fbff700>
??????? // <__NSGlobalBlock__: 0x1000010d0>
??? }
??? return 0;
}
????????可以看出,ARC對類型為strong且捕獲了外部變量的block進行了copy。并且當block類型為strong,但是創建時沒有捕獲外部變量,block最終會變成__NSGlobalBlock__類型(這里可能因為block中的代碼沒有捕獲外部變量,所以不需要在棧中開辟變量,也就是說,在編譯時,這個block的所有內容已經在代碼段中生成了,所以就把block的類型轉換為全局類型)。
1.7.2 block作為參數傳遞
????????再來看下使用在棧中的block需要注意的情況:
NSMutableArray *arrayM;
void myBlock()
{
??? int a = 5;
??? Block block = ^ {
??????? NSLog(@"%d", a);
??? };
??? [arrayM addObject: block];
??? NSLog(@"%@", block);
}
int main(int argc, const char* argv[]) {
??? @autoreleasepool{
??????? arrayM = @[].mutableCopy;
??????? myBlock();
??????? Block block = [arrayM firstObject];
??????? // 非ARC這里崩潰
??????? block();
?}
// ARC情況下輸出
// <__NSMallocBlock__: 0x100214480>
// 非ARC情況下輸出
// <__NSStackBlock__: 0x7fff5fbff738>
// 崩潰,野指針錯誤
????????可以看到,ARC情況下因為自動執行了copy,所以返回類型為__NSMallocBlock__,在函數結束后依然可以訪問;而非ARC情況下,需要我們手動調用[block copy]來將block拷貝到堆中,否則因為棧中的block生命周期和函數中的棧生命周期關聯,當函數退出后,相應的堆被銷毀,block也就不存在了。
????????如果把block的以下代碼刪除:
NSLog(@"%d", a);
????????那么block就會變成全局類型,在main中訪問也不會出崩潰。
1.7.3 block作為返回值
????????在非ARC情況下,如果返回值是block,則一般這樣操作:
return [[block copy] autorelease];
????????對于外部要使用的block,更趨向于把它拷貝到堆中,使其脫離棧生命周期的約束。
1.7.4 block屬性
????????這里還有一點關于block類型的ARC屬性。上文也說明了,ARC會自動幫strong類型且捕獲外部變量的block進行copy,所以在定義block類型的屬性時也可以使用strong,不一定使用copy。也就是以下代碼:
/** 假如有棧block賦給以下兩個屬性 **/
// 這里因為ARC,當棧block中會捕獲外部變量時,這個block會被copy進堆中
// 如果沒有捕獲外部變量,這個block會變為全局類型
// 不管怎么樣,它都脫離了棧生命周期的約束
@property (strong, nonatomic) Block *strongBlock;
// 這里都會被copy進堆中
@property (copy, nonatomic) Block *copyBlock;
1.8 Block機制總結
1、block是Objective-C環境下閉包實現的一種結構。
????實質也是基于結構體+函數指針+isa類指針實現,即是一個“仿”對象;
2、block聲明的生存期
???? 棧上(即函數內部)聲明對象是會被回收的,如果要長期持有block對象需要把她移到堆上,在ARC環境下oc會自動copy到堆上;
3、外部變量在block中使用時,要避免循環引用;
2 用法示例
2.1 參數是NSString*的代碼塊
void?(^printBlock)(NSString?*x);
printBlock?=?^(NSString*?str)
{
????NSLog(@"print:%@",?str);
};
printBlock(@"hello?world!");
運行結果是:
print:hello world!
2.2 代碼用在字符串數組排序
NSArray?*stringArray?=?[NSArray?arrayWithObjects:@"abc?1",?@"abc?21",?@"abc?12",@"abc?13",@"abc?05",nil];
NSComparator?sortBlock?=?^(id?string1,?id?string2)
{
????return?[string1?compare: string2];
};
NSArray?*sortArray?=?[stringArray?sortedArrayUsingComparator: sortBlock];
NSLog(@"sortArray:%@",?sortArray);??
運行結果:
sortArray:(
? ? "abc?05",
? ? "abc 1",
? ? "abc?12",
? ? "abc?13",
? ? "abc 21"
)
2.3 代碼塊的遞歸調用
??? ????代碼塊想要遞歸調用,代碼塊變量必須是全局變量或者是靜態變量,這樣在程序啟動的時候代碼塊變量就初始化了,可以遞歸調用。
static?void?(^?const?blocks)(int)?=?^(inti)
{
????if?(i?>?0)?{
????????NSLog(@"num:%d",?i);
????????blocks(i?-?1);
????}
};
blocks(3);?
運行打印結果:
num:3
num:2
num:1
2.4 在代碼塊中使用局部變量和全局變量
2.4.1 全局變量使用方式
????在代碼塊中可以使用和改變全局變量
int?global?=?1000;
int?main(int?argc,?const?char*?argv[])
{
????@autoreleasepool?{
????????????void(^block)(void)?=?^(void){
????????????????global++;
? ? ?????????????NSLog(@"global:%d",?global);
????????????};
????????block();
????????NSLog(@"global:%d",?global);
????}
????return?0;
}?
運行打印結果:
global:1001
global:1001
2.4.2 局部變量使用
????而局部變量可以使用,但是不能改變。
int?local?=?500;
void?(^block)(void)?=?^(void){
????local++;
????NSLog(@"local:%d",?local);
};
block();
NSLog(@"local:%d",?local);
????????在代碼塊中改變局部變量編譯不通過。怎么在代碼塊中改變局部變量呢?在局部變量前面加上關鍵字:__block????
__block?int?local?=?500;
void?(^block)(void)?=?^(void){
????local++;
????NSLog(@"local:%d",?local);
};
block();
NSLog(@"local:%d",?local);?
運行結果:
local:501
local:501
3 優劣分析
3.1 優劣簡介
????????block優勢:
????a.最?大限度地使?用上下?文變量和環境,便于參數靈活傳遞。
????b.內存安全(避免delegate弱引?用可能帶來的野指針問題)。
????c.使?方便,inline聲明。
????????block的劣勢:
????a.容易造成內存循環引?;
????b.如果是跨越棧幀的調?用,需要有copy操作(棧->堆);
????c.__block帶來的野指針問題。
3.2 與Delegate比較
????1. Prefer to use blocks when object is singleton as you cannot use delegation pattern. For example if you make two or three or more UIAlertViews in a UIViewController how will you able to know which UIAlerView did what. Better way will be to display UIAlerView with their completion handler?block.
????2. If you are targeting to write call backs for several steps of a process use Delegation. For example UIViewContoller Delegate tells viewWillLoad, viewDidLoad, viewWillAppear, viewDidAppear and keep telling until viewDidUnLoad. If you are targeting results of a process use Blocks -Blocks are generally useful to tell Failure or Success for example when you attempt to authenticate user with Game Center it tells in completion handle block[localPlayer authenticateWithCompletionHandler:^{**} either Success or Failure.????
????3. Prefer to use delegate pattern if an object has multiple distinct events.For example UITextField have multiple distinct events. It should tell when shouldBeginEditing, didBedingEditing, shouldEndEditing, didEndEditing, shouldReturn etc.
????4. If your call back needs some additional information from your object(view or ViewController) delegation pattern will be the best option. for exampleUITableView data source delegate methods require number of rows, number of sections and each cell to be returned from its owner object.
4 使用技巧
4.1 常用技巧
4.1.1 Block定義typedef
typedef void (^WVJBResponseCallback)(idresponseData);
typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
4.1.2 ios block中為什么要對weakSelf進行strong
????????在block中調用self會引起循環引用,但是在block中需要對weakSelf進行strong,保證代碼在執行到block中,self不會被釋放,當block執行完后,會自動釋放該strongSelf。
????block外面:
__weak __typeof(&*self)weakSelf = self
block中:
__strong __typeof(&*self)strongSelf = weakSelf;
strongSelf.frame = frame;
5 參考鏈接
(Very Good)Block技巧與底層解析
http://www.lxweimin.com/p/51d04b7639f1
(Good)談Objective-CBlock的實現
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
Objective-C語法之代碼塊(block)的使用
http://blog.csdn.net/totogo2010/article/details/7839061
IOS中Block簡介與用法(一)
http://blog.csdn.net/enuola/article/details/8674063
IOS block教程
http://www.cppblog.com/cokecoffe/archive/2012/05/31/176920.html
iOS深入學習(Block全面分析)
http://my.oschina.net/leejan97/blog/268536
iOS block的用法
http://www.cnblogs.com/jy578154186/archive/2012/09/30/2709130.html
ios block中為什么要對weakSelf進行strong
http://blog.csdn.net/zhuoyuetec/article/details/43405137
如何避免在Block里用self造成循環引用
http://blog.csdn.net/zhangao0086/article/details/38273239
到底什么時候才需要在ObjC的Block中使用weakSelf/strongSelf
http://www.360doc.com/content/15/0915/08/19175681_499222470.shtml
iOS閉包中的[weakself]在什么情況下需要使用,什么情況下可以不加?
http://www.zhihu.com/question/34593410
iOS中Block介紹(一)基礎
http://mobile.51cto.com/hot-403897.htm
iOS中Block介紹(二)內存管理與其他特性
http://mobile.51cto.com/hot-403914.htm
iOS中block介紹(三)揭開神秘面紗(上)
http://mobile.51cto.com/hot-403931.htm
iOS中block介紹(四)揭開神秘面紗(下)
http://mobile.51cto.com/hot-403935.htm
對Objective-C中Block的追探
http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html
Block的實現
blocks: Episode 3
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/Block_private.h
iOS中block實現的探究
http://blog.csdn.net/jasonblog/article/details/7756763
A look inside blocks: Episode 1
A look inside blocks: Episode 2