title: 理解Block
block是開發(fā)中經(jīng)常用到的一個對象橄镜,或者說一個方法必指。在很多編程語言中,都有閉包的概念株茶,block就是OC對閉包的實現(xiàn)来涨。
定義block
局部變量:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {…};
屬性:
@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
方法參數(shù):
- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
方法調(diào)用:
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
typedef定義:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...}
block究竟是什么
新建一個工程,我們在main函數(shù)里面寫一個最簡單的block
int main(int argc, char * argv[]) {
@autoreleasepool {
^{
int i = 0;
i++;
};
return 0;
}
}
然后用命令行將main.m這個文件轉(zhuǎn)換成C++代碼:clang -rewrite -objc main.m启盛,我們會在目錄下會得到一個main.cpp的文件蹦掐,打開我們會發(fā)現(xiàn)其實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) {
int i = 0;
i++;
}
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, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}
}
- 因為有isa指針,所以我們可以把block看做一個類對象僵闯,isa表明該block的類型卧抗,下面會講到;
- Flags是標(biāo)志變量鳖粟;
- Reserved是保留變量社裆;
- FuncPtr是block執(zhí)行時候調(diào)用的函數(shù)指針。
__block_impl是block的主體數(shù)據(jù)結(jié)構(gòu)牺弹,__main_block_impl_0可以看作是block的構(gòu)造函數(shù)浦马。在__main_block_impl_0中时呀,有__block_impl類型的impl张漂,用于存儲block的數(shù)據(jù)結(jié)構(gòu)晶默,還有個__main_block_desc_0類型的指針Desc,通過下面__main_block_desc_0的結(jié)構(gòu)體可以看出航攒,這是用于存儲block的保留字段和空間大小磺陡,另外還有個同名的初始化方法,方法含有三個參數(shù)漠畜,在main函數(shù)內(nèi)的調(diào)用過這個方法币他,對比一下我們可以看到,第一個參數(shù)是__main_block_func_0函數(shù)憔狞,正好是我們所寫block中的代碼塊蝴悉,第二個參數(shù)是__main_block_desc_0_DATA,即__main_block_desc_0的結(jié)構(gòu)體瘾敢,同時賦值為{ 0, sizeof(struct __main_block_impl_0)}拍冠,即reserved為0,Block_size字段為__main_block_impl_0結(jié)構(gòu)體的大小簇抵,第三個參數(shù)是一個缺省參數(shù)庆杜,在該函數(shù)內(nèi)部,可以看到確定了該block的類型碟摆,缺省參數(shù)flags賦值給了impl.Flags晃财,要執(zhí)行的代碼塊也賦值給了impl.FuncPtr,后期block在執(zhí)行的時候可以直接根據(jù)該指針調(diào)用典蜕。
block的類型
上文將main.m文件編譯成main.cpp中断盛,有這樣一句代碼,在__main_block_impl_0結(jié)構(gòu)體的__main_block_impl_0函數(shù)中:
impl.isa = &_NSConcreteStackBlock;
block的類型總共有三種:
- _NSConcreteStackBlock 棧block
- _NSConcreteMallocBlock 堆block
- _NSConcreteGlobalBlock 全局block
block的類型根據(jù)isa指針來確定的愉舔,在實際開發(fā)中如何寫這三種block呢郑临?如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int xxx = 100;
NSLog(@"globalBlock:%@",^(){
});
NSLog(@"stackBlock:%@",^(){
xxx;
});
void (^mallocBlock)(void) = ^(){
xxx;
};
NSLog(@"mallocBlock:%@", mallocBlock);
}
return 0;
}
打印結(jié)果:
globalBlock:<__NSGlobalBlock__: 0x100001060>
stackBlock:<__NSStackBlock__: 0x7fff5fbff728>
mallocBlock:<__NSMallocBlock__: 0x100400110>
可以發(fā)現(xiàn),globalBlock和stackBlock差別只是stackBlock的大括號之中一段引用了一個上下文變量a屑宠,stackBlock和mallocBlock的差別只是mallocBlock在stackBlock的基礎(chǔ)上賦值給了一個變量厢洞。
所以,一個不引用外部變量的block是_NSConcreteGlobalBlock典奉,對應(yīng)的躺翻,引用了外部變量的block則會分配在棧上成為_NSConcreteStackBlock,最后卫玖,將棧block賦值給一個變量公你,系統(tǒng)會自動拷貝到堆上,成為_NSConcreteMallocBlock假瞬。
關(guān)于__block
在實際開發(fā)中陕靠,開發(fā)者會通過block做回調(diào)傳值迂尝,也因此會修改外部的局部變量。實例代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int xxx = 100;
void (^aBlock)(void) = ^(){
xxx = 101;
};
}
return 0;
}
實際上這段代碼是編譯不過的剪芥,原因剛才提到過垄开,因為block不允許修改外部的局部變量。解決辦法很簡單税肪,在局部變量的聲明前加上__block關(guān)鍵字
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int xxx = 100;
void (^aBlock)(void) = ^(){
xxx = 101;
};
}
return 0;
}
為何加上__block就可以在block內(nèi)部修改局部變量a的值溉躲?我們做如下嘗試,在多個位置打印a的地址益兄。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int xxx = 100;
NSLog(@"before block xxx:%p", &xxx);
void (^aBlock)(void) = ^(){
xxx = 101;
NSLog(@"in block xxx:%p", &xxx);
};
aBlock();
NSLog(@"after block xxx:%p", &xxx);
NSLog(@"block:%@", aBlock);
}
return 0;
}
打印結(jié)果如下:
before block xxx:0x7fff5fbff748
in block xxx:0x100300388
after block xxx:0x100300388
block:<__NSMallocBlock__: 0x1003008b0>
此時將其編譯成.cpp文件:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_xxx_0 {
void *__isa;
__Block_byref_xxx_0 *__forwarding;
int __flags;
int __size;
int xxx;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_xxx_0 *xxx; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_xxx_0 *_xxx, int flags=0) : xxx(_xxx->__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_xxx_0 *xxx = __cself->xxx; // bound by ref
(xxx->__forwarding->xxx) = 101;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->xxx, (void*)src->xxx, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->xxx, 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;
__attribute__((__blocks__(byref))) __Block_byref_xxx_0 xxx = {(void*)0,(__Block_byref_xxx_0 *)&xxx, 0, sizeof(__Block_byref_xxx_0), 100};
void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_xxx_0 *)&xxx, 570425344));
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
我們會發(fā)現(xiàn)锻梳,用__block修飾的變量,會被轉(zhuǎn)化成一個結(jié)構(gòu)體:__Block_byref_xxx_0净捅,同時在第47行疑枯,聲明了一個該結(jié)構(gòu)體類型的變量,重新開辟空間以及賦值蛔六。那么該位置是在哪荆永,是在棧空間還是堆空間古今?
對比剛才的打印結(jié)果屁魏,before block ****xxx****:****0x7fff5fbff748,拷貝以后in block ****xxx****:****0x100300388捉腥,二者相差太多氓拼,反而拷貝后xxx的地址與存在堆內(nèi)存的block相差1M不到,由此可以推斷出抵碟,block在拷貝的過程中桃漾,將以__block修飾的局部變量也拷貝到了堆中。**
**
簡單地數(shù)值類型比較簡單拟逮,下面我們嘗試一下對象類型撬统。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSMutableString *a = [NSMutableString stringWithString:@"Tom"];
NSLog(@"before block:a:%p , %p", a, &a);
void (^foo)(void) = ^{
NSLog(@"in block:a:%p , %p", a, &a);
};
foo();
NSLog(@"after block:a:%p , %p", a, &a);
}
return 0;
}
打印結(jié)果:
before block:a:0x1003064f0 , 0x7fff5fbff748
in block: a:0x1003064f0 , 0x1003067a8
after block: a:0x1003064f0 , 0x1003067a8
可以發(fā)現(xiàn),a所指向堆中內(nèi)容的地址不變敦迄,變的是a本身的地址恋追。所以在block拷貝過程中,將原本在棧中的a拷貝到了堆上罚屋,a的內(nèi)容仍然是原本字符串的堆地址苦囱,因為由棧到堆,所以a本身的地址發(fā)生了變化脾猛,再結(jié)合剛才cpp文件中撕彤,__Block_byref_xxx_0結(jié)構(gòu)體中的__forwarding變量,無論是一開始還在棧的block猛拴,還是拷貝后在堆上的block羹铅,其__forwarding都是指向了堆上的block蚀狰,所以才能指向同一段字符串內(nèi)容。
__block在ARC和MRC下的區(qū)別
同樣是上面一段代碼职员,在MRC環(huán)境下麻蹋,打印出來的結(jié)果是什么呢?
before block:a:0x1003064a0 , 0x7fff5fbff748
in block: a:0x1003064a0 , 0x7fff5fbff748
after block: a:0x1003064a0 , 0x7fff5fbff748
和ARC環(huán)境下的不一樣廉邑,a本身的地址并沒有因為__block的修飾而變化哥蔚,也就是說倒谷,a仍然在棧上蛛蒙,并沒有拷貝到堆上。同樣在MRC環(huán)境下渤愁,我們再稍作修改牵祟。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSMutableString *a = [NSMutableString stringWithString:@"Tom"];
NSLog(@"before block:a:%p , %p", a, &a);
void (^foo)(void) = [^{
NSLog(@"in block:a:%p , %p", a, &a);
} copy];
foo();
NSLog(@"after block:a:%p , %p", a, &a);
}
return 0;
}
打印結(jié)果:
before block:a:0x100302f30 , 0x7fff5fbff748
in block: a:0x100302f30 , 0x100400308
after block: a:0x100302f30 , 0x100400308
在block賦值之前先拷貝一下,這時的效果跟ARC下就差不多了抖格。
總結(jié):MRC下block并沒有對__block修飾的局部變量進(jìn)行拷貝诺苹,只是一個弱引用;相反雹拄,ARC下block會拷貝該變量收奔,并對其retain。
block的函數(shù)性
如果從數(shù)據(jù)結(jié)構(gòu)的角度上說滓玖,因為block是結(jié)構(gòu)體指針坪哄,因為其有isa指針,那么在實際使用過程中势篡,我們更注重的是如何利用block進(jìn)行傳值和處理翩肌,其作用更像一個函數(shù)。
^(){
NSLog(@"block is kind of function too.");
}();
該段代碼可以直接運行禁悠,加上其可以作為參數(shù)和返回值念祭,完全可以實現(xiàn)類似于swift下真正意義上的block,此時碍侦,它就是一個函數(shù)粱坤。