Block作为OC语言对于闭包的实现,使用频率是非常高的。所有有必要深入理解。
一个简单的block
首先敲入一个简单的block,如:
//
// main.m
// MyConsole3
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
void (^TestBlock1)() = ^{ NSLog(@"empty block"); };
}
return 0;
}
我们可以使用CLang生成c语言中间代码。
如:
这样就会生成一个c语言的中间代码,当前目录下main.cpp,如:
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_2c51a5_mi_1); }
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;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_2c51a5_mi_0);
void (*TestBlock1)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)TestBlock1)->FuncPtr)((__block_impl *)TestBlock1);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我们可以看到里面有个结构,__main_block_impl_0,这个结构有2个数据成员和1个构造函数。其中第一个成员impl有个成员叫做isa,那么可以肯定那家伙是NSObject的子类,也就是说Block本身也是一个oc对象(NSObject子类)。
__main_block_func_0就是block的执行函数。这个例子只是打一个log。
__main_block_desc_0是对block的一个描述。
然后看main里面的代码:
void (*TestBlock1)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
上面这个是创建一个block,基本上可以看出这是创建了一个结构对象,通过调用构造函数,把block内容(函数)和一个描述给传了进去。这样就有了一个block对象。然后看这一行:
((void (*)(__block_impl *))((__block_impl *)TestBlock1)->FuncPtr)((__block_impl *)TestBlock1);
其实就是通过block对象的FuncPtr函数指针来调用函数,注意这个函数的第一个参数是block对象本身。如果看一下block对象里面的函数的原型,static void __main_block_func_0(struct __main_block_impl_0 *__cself),就会发现它的第一个参数__cself,类似与c++的this,或者oc的self吧。
(注意:像这种没有引用任何局部变量的block,会被运行时转换成Global的,在中间代码里面我们看到的是Stack,但是通过打印block对象信息,会发现是global的,看最后面)
Global Block
上面的block里面isa = &_NSConcreteStackBlock,可想而知,还有其他类型的block,我们把block当作一个全局的,又如何呢?简单的把block对象改成全局变量,如:
#import <Foundation/Foundation.h>
void (^TestBlock_Global)() = ^{ NSLog(@"empty block"); };
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
TestBlock_Global();
}
return 0;
}
这样,再使用clang(clang -rewrite-objc main.m),就可以得到中间代码如:
struct __TestBlock_Global_block_impl_0 {
struct __block_impl impl;
struct __TestBlock_Global_block_desc_0* Desc;
__TestBlock_Global_block_impl_0(void *fp, struct __TestBlock_Global_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock_Global_block_func_0(struct __TestBlock_Global_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_18b7cc_mi_0); }
static struct __TestBlock_Global_block_desc_0 {
size_t reserved;
size_t Block_size;
} __TestBlock_Global_block_desc_0_DATA = { 0, sizeof(struct __TestBlock_Global_block_impl_0)};
static __TestBlock_Global_block_impl_0 __global_TestBlock_Global_block_impl_0((void *)__TestBlock_Global_block_func_0, &__TestBlock_Global_block_desc_0_DATA);
void (*TestBlock_Global)() = (void (*)())&__global_TestBlock_Global_block_impl_0;
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)(__block_impl *))((__block_impl *)TestBlock_Global)->FuncPtr)((__block_impl *)TestBlock_Global);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
和前面一个中间代码相比较,唯一的差别就是
impl.isa = &_NSConcreteGlobalBlock;
说明这是一个global的block了。而前面的block存在于栈上。
栈block获取局部变量
block可以接收局部变量,如下面的代码。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a = 10;
void (^TestBlock)() = ^{ NSLog(@"value: %d", a); };
TestBlock();
a = 20;
TestBlock();
}
return 0;
}
上面的例子代码调用block两次,那么a的值分别是多少呢?
运行一下,会发现都是10,为什么呢?同样,我们再用clang产生中间代码看看:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_5d8937_mi_0, a); }
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;
int a = 10;
void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
a = 20;
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
可以清楚的看到block结构里面多了一个成员int a,然后构造函数也多了一个参数int _a,看main里面的block对象创建,可以清楚的看到把局部变量a通过构造函数传进去了,也就是说等block对象创建完的时候,对象里面的数据成员已经保存了a的值。那么尽管第二次调用的时候,a改成了20,但是block对象里面保存的值还是10, 所以,当调用block主体的时候,还是显示10,看int a= __cself->a。明显block主体调用的时候,a的值是从block对象里面取出来的。所以,两次调用都是10.
那么如果我们通过地址来访问呢?又如何?
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a = 10;
int* p = &a;
void (^TestBlock)() = ^{ NSLog(@"value: %d", *p); };
TestBlock();
a = 20;
TestBlock();
}
return 0;
}
两次调用输出什么呢?运行结果是10和20.那这又是如何解释呢?同样,搬出clang
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *p;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_260c4b_mi_0, *p); }
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;
int a = 10;
int* p = &a;
void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
a = 20;
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
区别就是block结构里面的数据成员变成了int*,这样的话,等于在创建block对象的时候,a的地址就传给了block对象进行保存,那么这个block对象里面的保存的a的地址就在创建的时候确定了。然后等第二次调用的时候,我们修改了a的值,block主体运行的时候,通过a的地址读取a的值,所以是20了。但是这么做有个问题,a的值是放在stack上的,如果block的运行是在出了a作用域以外的地方(比如block当作回调的时候),会发生什么事情?那个时候a对象已经释放了,然后又通过地址去访问,鬼知道的会发生什么,所以一定要注意。
静态,全局变量
如果我们使用静态变量或者全局变量的?如:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
static int a = 10;
void (^TestBlock)() = ^{ NSLog(@"value: %d", a); };
TestBlock();
a = 20;
TestBlock();
}
return 0;
}
运行结果,输出10和20.为什么加了个static,就不一样了呢?
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_dc2044_mi_0, (*a)); }
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;
static int a = 10;
void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
a = 20;
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
明白了,原来是静态局部变量的时候,创建block对象的时候,传的是地址,block结构里面保存的也是int*,block主体运行的时候是通过指针来获取的,所以第二次调用就是20了。
那么如果把a改成全局变量呢?直接看中间代码:
int a = 10;
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_d24d32_mi_0, a); }
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 (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
a = 20;
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
当block主体__main_block_func_0调用的时候,直接访问全局变量a,而不是像局部变量一样通过int a = __cself->a,所以,第二次调用是20.
到目前为止我们访问的都是int简单类型,那么对于oc类型又如何呢?看下面的例子:
Block访问oc对象
把代码改成:
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property(nonatomic, readwrite, retain) NSString* name;
@end
@implementation Car
-(id) retain
{
return [super retain];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Car* car = [[Car alloc] init];
car.name = @"BMW";
void (^TestBlock)() = ^{ NSLog(@"static value: %@", car.name); };
TestBlock();
car.name = @"Benz";
TestBlock();
}
return 0;
}
结果就是输出BMW和Benz,用clang分析一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Car *car;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Car *_car, int flags=0) : car(_car) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Car *car = __cself->car; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_0a5585_mi_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)car, sel_registerName("name"))); }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->car, (void*)src->car, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->car, 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), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Car* car = ((Car *(*)(id, SEL))(void *)objc_msgSend)((id)((Car *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Car"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)car, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_0a5585_mi_0);
void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, car, 570425344);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)car, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_0a5585_mi_2);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
}
return 0;
}
所有的oc对象都是在heap 上的,也就是都是通过指针的方式来使用的,在创建block对象的时候,清楚的看到传递的是car,它就是一个指针,而block对象里面保存的也是Car* car,所以,跟int*一样,都是通过地址来访问的,那么地址上的内容变化了,它当然也可以访问到新的数据。所以输出BMW和Benz.
前面介绍传递a地址的时候讲到一个问题,就是万一当block运行的时候,a对象已经被释放了,会怎么样?结果是不可预测的。那么对于oc对象呢,也一样的问题。
比如:
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property(nonatomic, readwrite, retain) NSString* name;
@end
@implementation Car
-(id) retain
{
return [super retain];
}
- (oneway void) release
{
[super release];
}
- (void) dealloc
{
[super dealloc];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Car* car = [[Car alloc] init];
car.name = @"BMW";
void (^TestBlock)() = ^{ NSLog(@"static value: %@", car.name); };
TestBlock();
[car release];
// car.name = @"Benz";
TestBlock();
}
return 0;
}
我们第一次调用TestBlock后,把car给释放了,那么第二次调用TestBlock会怎么样呢?很明显这个时候car对象已经被释放了,然后第二次调用又通过地址去访问,肯定有问题。至于什么问题,就不一定了,如果那块内存被系统回收了,用于其他什么地方了,那么可能crash,也可能引发其他的异常。如果还没有回收,那么就运行正常。
所以当我们在block里面引用oc对象的时候,一定要注意周明周期,如果说当block被调用的oc对象已经被释放了,将会发生不可预测的问题。
Global Block & Stack Block
一般情况下,block都是stack block,如果block在函数外面或者block里面没有引用局部变量等,那么就是global的。global block在编译的时候就产生了,而stack block是动态在stack上创建的。如:第一个block只是打印一个log,没有任何局部变量引用,所以是global block,第二个引用了一个局部变量,所以是stack bloc。
还有另外一种block,heap block,待续。