在iOS4出來后,蘋果公司在OC中推出了block機制(也許更早就有了)。并且在后續(xù)的版本中大量的推廣和使用了這項技術,比如對視圖動畫API的改版,比如GCD技術等等。block技術并不是什么新技術,他的本質就是閉包功能在iOS上的實現(xiàn)而已。而閉包功能在其他很多語言中都有實現(xiàn),比如JAVA中接口的匿名實現(xiàn)。用閉包可以解決那些執(zhí)行邏輯和上下文環(huán)境解耦的場景,如果從設計模式的角度來考慮的話閉包就是一種策略模式(Strategy)的實現(xiàn)。
本文并不探討如何應用block,而是探討OC的block機制是如何實現(xiàn)的。從代碼的角度來說block的出現(xiàn)和我們平時基于函數(shù)和類方法的編程方式不太一致,有時候甚至不好去理解,因為他可以在我們的代碼塊中定義代碼塊,而且新定義的代碼塊又不會按函數(shù)內的指令順序去執(zhí)行。我們可以大膽的設想,如果是要你去實現(xiàn)一套block機制,你會怎么去做?這也是本文要探討的東西,只有你知道了OC實現(xiàn)block的內幕,你才能夠更好的利用他。
寫這篇文章來分析原理時我隱去了一些細節(jié),而且有些結構體的定義也和真實的有差異,但是總體是正確的,目的是為了更好的了解到本質的東西。我們先來看下面一段含有block的OC代碼:
//文件test.m
#import <Foundation/Foundation.h>
void test()
{
//下面分別定義各種類型的變量
int a = 10; //普通變量
__block int b = 20; //帶__block修飾符的block普通變量
NSString *str = @"123";
__block NSString *blockStr = str; //帶__block修飾符的block OC變量
NSString *strongStr = @"456"; //默認是__strong修飾的OC變量
__weak NSString *weakStr = @"789"; //帶__weak修飾的OC變量
//定義一個block塊并帶一個參數(shù)
void (^testBlock)(int) = ^(int c){
int d = a + b + c;
NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
};
a = 20; //修改值不會影響testBlock內的計算結果
b = 40; //修改值會影響testBlock內的計算結果。
testBlock(30); //執(zhí)行block代碼。
}
- 上面的代碼片段中,我們分別定義了:
- 不帶修飾符的基本類型變量a
- 帶__block修飾符的block變量b和blockStr
- 默認帶__strong修飾符的變量strongStr
- 帶__weak修飾符的變量weakStr
這些修飾符關鍵字的使用會對block塊內的代碼在運行時產(chǎn)生不同的影響。就上面的代碼片段而言當我們在編譯時,編譯器到底做了什么處理?如果能夠了解到編譯器的編譯過程,那么對我們掌握其實現(xiàn)機制就非常有幫助。幸好我們可以借助命令來看到這個中間的過程,您可以打開終端控制臺,并到test.m文件所在的路徑下執(zhí)行如下的命令:
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations test.m
clang這個命令會在相同目錄下產(chǎn)生一個test.cpp的文件。這個文件是OC代碼的C++實現(xiàn)版本,因為我們知道C++是不支持閉包技術的,因此您可以通過查看test.cpp這個文件來了解到OC中的閉包技術到底是如何用函數(shù)和結構體來實現(xiàn)的。我們可以先來看看test.cpp的部分實現(xiàn):
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
struct __Block_byref_blockStr_1 {
void *__isa;
__Block_byref_blockStr_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *blockStr;
};
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int a;
NSString *strongStr;
NSString *weakStr;
__Block_byref_b_0 *b; // by ref
__Block_byref_blockStr_1 *blockStr; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, NSString *_strongStr, NSString *_weakStr, __Block_byref_b_0 *_b, __Block_byref_blockStr_1 *_blockStr, int flags=0) : a(_a), strongStr(_strongStr), weakStr(_weakStr), b(_b->__forwarding), blockStr(_blockStr->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself, int c) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
__Block_byref_blockStr_1 *blockStr = __cself->blockStr; // bound by ref
int a = __cself->a; // bound by copy
NSString *strongStr = __cself->strongStr; // bound by copy
NSString *weakStr = __cself->weakStr; // bound by copy
int d = a + (b->__forwarding->b) + c;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_3, d, strongStr, (blockStr->__forwarding->blockStr), weakStr);
}
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->strongStr, (void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->blockStr, (void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakStr, (void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test()
{
int a = 10;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_0;
__attribute__((__blocks__(byref))) __Block_byref_blockStr_1 blockStr = {(void*)0,(__Block_byref_blockStr_1 *)&blockStr, 33554432, sizeof(__Block_byref_blockStr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, str};
NSString *strongStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_1;
NSString *weakStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_2;
void (*testBlock)(int) = ((void (*)(int))&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, strongStr, weakStr, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&blockStr, 570425344));
a = 20;
(b.__forwarding->b) = 40;
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
上面的代碼對于某些同學來說也許太過晦澀了!不過沒有關系,我把上面的代碼進行簡化和處理,并去掉了一些次要的東西,然后簡化為如下的代碼:
// 每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是_block int b 的結構體定義:
struct Block_b {
void *isa; //固定為NULL
Block_b *forwarding; //指向真正的block對象變量。
int flags;
int size; //結構體的size
int b; //保存代碼中定義的變量值。
};
//每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是 __block NString *blockStr 的結構體定義:
struct Block_blockStr {
void *isa; //固定為NULL
Block_blockStr *forwarding; //指向真正的block對象變量
int flags;
int size; //結構體的size
NSString * blockStr; //保存代碼中定義的變量值。
};
//每個block塊都會生成一個和OC類內存結構兼容的結構體和一個描述這個block塊信息描述的結構體
struct Block_testBlock {
//所有block塊的固定部分,這也是一個OC類的內存結構。
Class isa; //block的OC類型
int flags;
int reserved;
void *funcPtr; //block塊函數(shù)的地址。
Block_testBlock_Desc* desc; //block的描述信息。
//所有在block代碼塊內引用的外部數(shù)據(jù)都會成為結構體內的數(shù)據(jù)成員。
int a;
NSString * strongStr;
NSString * __weak weakStr;
Block_b *b;
Block_blockStr *blockStr;
//結構體的構造函數(shù)。
Block_testBlock(void *_funcPtr, Block_testBlock_Desc *_desc, int _a, NSString * _strongStr, NSString * _weakStr, struct Block_b *_b, Block_blockStr *_blockStr, int _flags)
{
isa = &_NSConcreteStackBlock; //根據(jù)具體的block類型賦值。
flags = _flags;
reserved = 0;
funcPtr = _funcPtr;
desc = _desc;
a = _a;
strongStr = _strongStr;
weakStr = _weakStr;
b = _b->forwarding; //b保存真實的block變量的地址。
blockStr = _blockStr->forwarding; //blockStr保存真實的block變量的地址。
}
};
//block塊信息描述的結構體定義,主要有block對象的尺寸,以及block中函數(shù)的參數(shù)信息,也就是參數(shù)的簽名信息。并生成一個全局的常量對象_testBlock_desc_DATA
struct Block_testBlock_Desc {
unsigned long reserved;
unsigned long size; //塊的尺寸
void *rest[1]; //塊的參數(shù)簽名信息
}_testBlock_desc_DATA = {0, sizeof(Block_testBlock), "v12@?0i8"};
//這部分是block塊函數(shù)體的定義部分,可以看出block的代碼塊都轉化為了普通的函數(shù),并且函數(shù)會默認增加一個隱藏的__cself參數(shù),用來指向block對象本身。
static void testBlockfn(Block_testBlock *__cself, int c) {
//還原函數(shù)體內引用外部的數(shù)據(jù)對象和變量。
Block_b *b = __cself->b;
Block_blockStr *blockStr = __cself->blockStr;
int a = __cself->a;
NSString *__strong strongStr = __cself->strongStr;
NSString *__weak weakStr = __cself->weakStr;
//int d = a + b + c;
int d = a + b->forwarding->b + c; //注意這里block變量使用方式。
//NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr->forwarding->blockStr, weakStr);
}
void test()
{
int a = 10;
//__block int b = 20;
Block_b b = {nil, &b, 0, sizeof(struct Block_b), 20};
// __block NSString *blockStr = @"123";
Block_blockStr blockStr = {nil, &blockStr, 33554432, sizeof(Block_blockStr), @"123"};
NSString *strongStr = @"456";
__weak NSString *weakStr = @"789";
//每個在代碼中的block塊都會生成對應的OC block對象,這里面用構造函數(shù)初始化這個block對象。
Block_testBlock testBlock(&testBlockfn, &_testBlock_desc_DATA, a, strongStr, weakStr, &b, &blockStr, 570425344);
a = 20; //這個不會影響到block塊內執(zhí)行時a的值。
// b = 40; 這個賦值會影響到block塊內執(zhí)行時b的值。
b.forwarding->b = 40; //注意__block類型變量的值的更新方式。
//執(zhí)行block塊其實就是執(zhí)行block對象里面的函數(shù)。
//testBlock(30);
testBlock.funcPtr(&testBlock, 30);
}
先看函數(shù)test內的實現(xiàn)部分,我們發(fā)現(xiàn)所有帶 __block修飾符的變量的定義由:
__block int b = 20;
__block NSString *blockStr = @"123";
變?yōu)榱耍?/p>
Block_b b = {nil, &b, 0, sizeof(struct Block_b), 20};
Block_blockStr blockStr = {nil, &blockStr, 33554432, sizeof(Block_blockStr), @"123"};
也就是說所有定義為__block類型的變量,在編譯時都會變?yōu)橐粋€個block對象變量。在編譯時系統(tǒng)會為每個帶__block修飾的變量生成一個和OC類內存結構兼容的結構體:
// 每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是_block int b 的結構體定義:
struct Block_b {
void *isa;
Block_b *forwarding; //指向真正的block對象變量。
int flags;
int size; //結構體的size。
int b; //保存代碼中定義的變量。
};
//每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是 __block NString *blockStr 的結構體定義:
struct Block_blockStr {
void *isa;
Block_blockStr *forwarding; //指向真正的block對象變量。
int flags;
int size;
NSString * blockStr; //保存代碼中定義的變量。
};
上面的兩個結構體都有固定的格式,而且也和OC類的內存結構匹配。也就是說當定義__block修飾的變量時,系統(tǒng)會把他轉化為一個OC對象。 為什么要把__block定義的變量轉變?yōu)镺C對象呢?這個是和__block這個關鍵字所表達的意思是一致的,也就是定義為__block類型的變量是不會在block代碼塊內產(chǎn)生副本的,而是保持唯一性。每個block對象變量的isa都固定設置為nil; 而forwarding則是指向真正操作的block對象變量,如果某個block對象變量只是在一個棧block對象里面被使用則這時候forwarding是指向block對象變量自己,而如果這個block對象變量在一個堆block對象里面被使用則這時候forwarding則是指向一個堆block對象變量的地址。
再來看test函數(shù)中的block塊的定義部分。從代碼中可以發(fā)現(xiàn)原先在代碼中定義的block塊,被拆分為了block對象和全局函數(shù)兩部分來實現(xiàn)。因此可以看出在iOS內所有定義的block代碼塊系統(tǒng)在編譯時都會轉化為個OC對象(NSBlock類是用來描述block代碼塊的OC類,系統(tǒng)一共支持棧block:NSStackBlock,堆block:NSMallocBlock,全局block:NSGlobalBlock三種類型的block。具體的細節(jié)和差異不在本文展開,請大家自行查找相關的資料。)。因此在編譯時我們會為每個block代碼塊都生成一個和OC類兼容的結構體,在我們的例子里面的結構體定義如下:
//每個block塊都會生成一個和OC類兼容的結構體。
struct Block_testBlock {
//前面5個數(shù)據(jù)成員在所有block定義中都相同,并且和OC兼容。
Class isa;
int flags;
int reserved;
void *funcPtr; //block塊的全局函數(shù)的地址。
Block_testBlock_Desc* desc; //block的描述。
//所有在block代碼塊引用的外部數(shù)據(jù)都會成為結構體的同名數(shù)據(jù)成員。
int a;
NSString * strongStr;
NSString * __weak weakStr;
Block_b *b;
Block_blockStr *blockStr;
//結構體的構造函數(shù)。
Block_testBlock(void *_funcPtr, Block_testBlock_Desc *_desc, int _a, NSString * _strongStr, NSString * _weakStr, struct Block_b *_b, Block_blockStr *_blockStr, int _flags)
{
isa = &_NSConcreteStackBlock; //根據(jù)具體的block類型賦值。
flags = _flags;
reserved = 0;
funcPtr = _funcPtr;
desc = _desc;
a = _a;
strongStr = _strongStr;
weakStr = _weakStr;
b = _b->forwarding; //其實就是指向自己
blockStr = _blockStr->forwarding;
}
};
//每個block塊的描述信息結構體,主要是保存block的尺寸,以及block中函數(shù)的參數(shù)信息。
struct Block_testBlock_Desc {
unsigned long reserved;
unsigned long size; //塊的尺寸
void *rest[1]; //塊的參數(shù)簽名信息
}_testBlock_desc_DATA = {0, sizeof(Block_testBlock), "v12@?0i8"};
可以看出我們定義的block代碼塊都會生成2個結構體:
Block_testBlock用來保存block的信息以及block內部要用到的所有數(shù)據(jù)。所有block對象結構體的前5個數(shù)據(jù)成員都是一致的,也就是和OC類的內存結構是兼容的。其中的isa用來保存block的類信息,這里面的類信息會根據(jù)block所處的位置的不同而不同。而后面的5個數(shù)據(jù)成員就是在block代碼塊內使用外部對象的副本。正是因為每個block對象在編譯時保存了代碼塊內使用代碼塊外的對象的副本,所以我們才能在后續(xù)代碼執(zhí)行時能夠訪問到這些信息。
Block_testBlock_Desc用來描述這個block的size以及block方法的參數(shù)的簽名信息。
下面就是Block_testBlock 實例的構造方法:
//每個在代碼中的block塊都會生成對應的OC block對象,這里面構造函數(shù)初始化這個block對象。
Block_testBlock testBlock(&testBlockfn, &_testBlock_desc_DATA, a, strongStr, weakStr, &b, &blockStr, 570425344);
上面可以看出,一旦在代碼中出現(xiàn)了block代碼塊,編譯時就會建立一個block對象,然后將block對象關聯(lián)的函數(shù)代碼地址、以及使用的外面的數(shù)據(jù)作為block對象的構造函數(shù)的參數(shù)來創(chuàng)建這個block對象。
最后我們再來考察block代碼的全局函數(shù)的實現(xiàn):
//這部分是block代碼函數(shù)體的定義部分,可以看見函數(shù)默認增加一個隱藏的__cself參數(shù)。
static void testBlockfn(Block_testBlock *__cself, int c) {
//還原函數(shù)體內引用外面的變量。
Block_b *b = __cself->b;
Block_blockStr *blockStr = __cself->blockStr;
int a = __cself->a;
NSString *__strong strongStr = __cself->strongStr;
NSString *__weak weakStr = __cself->weakStr;
//int d = a + b + c;
int d = a + (b->forwarding->b) + c;
//NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr->forwarding->blockStr, weakStr);
}
上面的代碼片段中,可以看出block塊全局函數(shù)除了定義的int類型參數(shù)外,還增加了一個隱藏的參數(shù)__cself用來指向block對象。然后在函數(shù)體的開始位置把使用的外部數(shù)據(jù)的副本還原到函數(shù)的棧內。這也是為什么我們能在block代碼塊內用到外面的數(shù)據(jù)的原因了。這里我們需要進一步考察這幾個副本的意義:
對于基本類型a的副本來說就是完全的內存拷貝,因此在block代碼塊內更新這些數(shù)據(jù)是不會影響到外面,同時外面的更新也不會影響到里面了。
對于對象類型的strongStr和weakStr而言這個副本只是指針的拷貝而不是所指對象的拷貝,因此在block代碼塊內能夠讀取最新的屬性和設置新的屬性值。
對于__block類型的對象來說,你會發(fā)現(xiàn)他也是指針的拷貝,所以也不會產(chǎn)生多份內存副本,同時可以看出對__block類型數(shù)據(jù)的讀取和設置我們都是間接來完成的,因此這里代碼塊內更新數(shù)據(jù)能影響外面,同時外面的更新也能影響里面。
好了,所有我要介紹的內容就到這里了,上面就是iOS的block的內部實現(xiàn)機制。我相信通過我上面的介紹能夠讓你了解到了block在編譯時所做的事情,以及能夠了解到__block, __weak, __strong各種修飾符的意義和差別。
。
在iOS4出來后,蘋果公司在OC中推出了block機制(也許更早就有了)。并且在后續(xù)的版本中大量的推廣和使用了這項技術,比如對視圖動畫API的改版,比如GCD技術等等。block技術并不是什么新技術,他的本質就是閉包功能在iOS上的實現(xiàn)而已。而閉包功能在其他很多語言中都有實現(xiàn),比如JAVA中接口的匿名實現(xiàn)。用閉包可以解決那些執(zhí)行邏輯和上下文環(huán)境解耦的場景,如果從設計模式的角度來考慮的話閉包就是一種策略模式(Strategy)的實現(xiàn)。
作者:歐陽大哥2013
鏈接:http://www.itdecent.cn/p/595a1776ba3a