Block
- 概念:Block是C语言的扩充功能.是带有自动变量(局部变量)的匿名函数
Block的实质
- 源码
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
- 可以看出block是一个结构体, 现在我们分析一个具体的myBlock
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
void(^myBlock)(void) = ^() {
};
myBlock();
}
return 0;
}
- clang -rewrite-objc之后
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;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pv_z69915_n7y703_tcrj_fsw7h0000gp_T_main_9174ff_mi_0);
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
- 我们将 __main_block_impl_0分为3个部分看,由于构造函数也一并写入,看起来很复杂,去掉该构造函数之后,可以看出结构体__main_block_impl_0的具体信息:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
- 我们展开第一个__block_impl结构体的声明
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- 第二个__main_block_desc_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
- 构造函数:
__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;
}
-
以上就是初始化__main_block_impl_0的源代码.
-
我们再看__main_block_impl_0构造函数的调用
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 去掉转换部分 ->
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &tmp;
-
这样就容易理解多了.其实就是将结构体实例的指针赋值给变量myBlock.
-
再看调用myBlock的部分
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
// 去掉转换->
(*myBlock->impl.FuncPtr)(myBlock)
- 这就是简单的使用函数指针调用函数.
- 另外我们可以在Block结构体中看到isa,说明Block其实是Objective-C对象.
- Block结构体中flags含义
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
- FuncPtr = ft, 也就是main函数里传过来的. __main_block_func_0. 是block被调用时执行的函数指针
- desc 是 __main_block_desc_0_DATA. 是对block的描述.对应这个:
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)};
外部变量
- 那么block是如何捕获外部变量的?
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSInteger a = 10;
void(^myBlock)(void) = ^() {
NSLog(@"a = %ld", a);
};
myBlock();
}
return 0;
}
- clang之后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSInteger a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _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) {
NSInteger a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pv_z69915_n7y703_tcrj_fsw7h0000gp_T_main_12aee4_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;
NSInteger a = 10;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
- 可以看出我们的局部变量a是传到struct内部作为block的成员变量存储的.
- 我们都知道在block内部是是不能改变a的值的.如果在block内改变外部变量的值怎么办呢?
__block说明符
- 有两种方法在block中改变外部变量.第一种:
int global_value = 1;
static int static_global_value = 2;
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int static_value = 3;
void(^blk)(void) = ^() {
global_value *= 1;
static_global_value *= 2;
static_value *= 3;
};
}
return 0;
}
转换后:
int global_value = 1;
static int static_global_value = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_value;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_value, int flags=0) : static_value(_static_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_value = __cself->static_value; // bound by copy
global_value *= 1;
static_global_value *= 2;
(*static_value) *= 3;
}
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 static_value = 3;
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_value));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
-
global_value和static_global_value和转换前的访问完全一样.只有static_value发生了转换.将static_value的指针传递给构造函数并保存在结构体的成员变量中.
-
在block中改变值的另一种办法就是使用__block存储域类说明符.类似于static, auto等说明符,他们用于指定将变量值设置到哪个存储域中.auto-栈, static-数据区.
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int blk_value = 3;
void(^blk)(void) = ^() {
blk_value *= 3;
};
}
return 0;
}
转换后:
struct __Block_byref_blk_value_0 {
void *__isa;
__Block_byref_blk_value_0 *__forwarding;
int __flags;
int __size;
int blk_value;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_blk_value_0 *blk_value; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_blk_value_0 *_blk_value, int flags=0) : blk_value(_blk_value->__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_blk_value_0 *blk_value = __cself->blk_value; // bound by ref
(blk_value->__forwarding->blk_value) *= 3;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blk_value, (void*)src->blk_value, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blk_value, 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_blk_value_0 blk_value = {(void*)0,(__Block_byref_blk_value_0 *)&blk_value, 0, sizeof(__Block_byref_blk_value_0), 3};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_blk_value_0 *)&blk_value, 570425344));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
-
加上__block之后代码量激增.首先blk_value变为结构体类型__Block_byref_blk_value_0的自动变量,而真正的值存在该结构体实例的成员变量blk_value中. 这也回答了为什么__block的变量可以在block内部被改变的原因:
- 为了在block内部访问到blk_value,我们需要把blk_value复制到堆区,所以我们实例化一个__Block_byref_blk_value_0结构体实例.然后将值存到该结构体的成员变量中.然后将该结构体实例的地址传给Block的构造函数.
-
blk_value *= 3 其实取的是__Block_byref_blk_value_0结构体实例内部的blk_value的值
- 转换:
blk_value->__forwarding->blk_value) *= 3; -
为什么会有成员变量__forwarding呢?
Block的存储域
- 有3种Block.这3种block的存储域如下图所示
| 类 | 设置对象的存储域 |
|---|---|
| _NSConcreteStackBlock | 栈 |
| _NSConcreteGlobalBlock | 程序的数据区 (.data区) |
| _NSConcreteMallocBlock | 堆 |
-
怎么知道我们的Block是哪种Block呢?
-
_NSConcreteGlobalBlock
- 记述全局变量的地方有Block语法时:
void (^blk)(void) = ^{NSLog(@"Hello World");}; int main { return 0; }- Block语法的表达式中不使用应截获的自动变量时:
int a = 10; void(^blk)(void) = ^() { // 这里不使用a NSLog(@"hello world"); }; blk(); -
除此之外都为_NSConcreteStackBlock.
-
_NSConcreteMallocBlock:
- 复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa.
- 上一节成员变量__forwarding存在的作用:__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上,还是堆上,都能正确的访问__block变量.
-
-
什么时候会将Block复制到堆上呢?
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block复制给附有__strong修饰id类型的类或Block类型成员变量时.
- 在方法名中含有suingBlock的Cocoa框架方法或者GCD的API传递Block时
-
一个Block使用多个__block变量,在该Block复制到堆时,其所有__block变量也会一并复制到堆上
- 多个Block使用__block变量, 任何一个Block复制到堆时,其Block也一并复制到堆,并被该Block持有.剩下的Block复制到堆时,被复制的Block持有__block变量并增加__block的引用计数.
- 变量__forwarding可以实现无论__block变量配置在栈上,还是堆上,都能正确的访问__block变量. 重新理解一下这句话: 也就是当Block0复制到堆,但是Block1还没复制到堆的这一时刻.Block1内部的__forwarding会指向已经复制到堆的__block变量结构体. 如图所示:
截获对象
- 如下代码会打印什么?
typedef void (^blk_t)(id);
int main(int argc, const char * argv[]) {
@autoreleasepool {
blk_t blk;
{
id array = [NSMutableArray array];
blk = ^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
return 0;
}
答案是:
array count = 1
array count = 2
- 该代码通过编译器转换后:
typedef void (*blk_t)(id);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pv_z69915_n7y703_tcrj_fsw7h0000gp_T_main_7454ed_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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;
blk_t blk;
{
id array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
在Block复制到堆时,通过__main_block_copy_0 也就是copy方法 使堆Block持有array. 同理在堆Block释放时,通过__main_block_dispose_0方法释放对array的持有.
总结
- 通过Block的isa我们知道Block是Objective-C对象.有3种Block
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
- Block内使用的变量局部变量和静态变量会存在Block结构体成员变量中.
- __block变量会被变为结构体类型__Block_byref_blk_value_0,被Block持有.
- 当Block被复制到堆时, 被其持有的__block变量也会被复制到堆.
- 当Block内使用了带有__strong修饰符的id类型或对象类型变量时, 编译器会自动增加copy和dispose方法,当Block被赋值到堆时被调用.copy和dispose方法是为了使其被堆Block持有和释放.