block
block
其实封装了函数调用以及函数调用环境的OC对象,也是一个OC对象,它内部也有个isa指针
int age = 20;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
通过clang
指令将其转成C语言函数看看生成了什么东西
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
生成了这些
int age = 20;
void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
查看__main_block_impl_0底层,是一个结构体定义
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结构体的第一个成员__block_impl再查看底层,也是一个结构体变量
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 指向了将来要执行block代码的函数地址
};
两者合并以及移除一些不相干的东西
struct __main_block_desc_0 {
size_t reserved; // 没什么用
size_t Block_size; // 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;
int age;
};
以上就是block的生成内容,总结来说block就是一个OC对象,isa也是一个对象,OC对象封装了函数的地址、block大小以及外部的局部变量
block 本质
定义一个这样的block,利用clang
指令生成C++代码看看是什么:
void (^block)(void) = ^{
NSLog(@"Hello, World!");
};
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;
// 构造函数(类似于OC的init方法),返回结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // 指向block的类型
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_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)};
// __main_block_impl_0返回了一个结构体,在将结构体的指针赋值给了block
// -------定义block变量-------------
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
// 找到block的FunPtr,执行block内部的代码
block->FuncPtr(block);
block 变量捕获
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
假设有这样一段代码,执行结果NSLog会输出age is 10
,为什么输出10呢,这就需要了解block的变量捕获机制了
int age = 10;
void (^block)(void) = ^{
// age的值捕获进来(capture)
NSLog(@"age is %d", age);
};
age = 20;
block();
利用clang
命令生成代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
// age(_age) 代表传进来的_age参数会自动赋值给age成员
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sy_rn7c3q7j76d2vf92dj0yl1g00000gn_T_main_0b78ef_mi_0, age);
}
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 age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
这时候发现block内部多了一个age的成员,传进来的age参数将会赋值给block内部的age成员
那假如是static类型变量呢?
// auto:自动变量,离开作用域就销毁
int weight = 10; // 也代表auto意思
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
// age的值捕获进来(capture)
NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20; // block将不会捕获static变量,使用指针访问的方式,因此height能够修改
block();
输出:age is 10, height is 20
查看C++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sy_rn7c3q7j76d2vf92dj0yl1g00000gn_T_main_7bfbdf_mi_0, age, (*height));
}
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 weight = 10;
auto int age = 10;
static int height = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 20;
height = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
注意看height访问方式,是使用*访问
总结如下:
block 类型
block也有类型, 可以通过class查看具体的类型:
// __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
// -------------输出为:-------------
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject
可以看到block最终也是继承自NSObject,也是一个OC对象
block的其他类型取决于变量访问方式
// 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
void (^block1)(void) = ^{
NSLog(@"Hello");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d", age);
};
NSLog(@"类型为:%@ %@ %@", [block1 class], [block2 class], [^{
NSLog(@"%d", age);
} class]);
// 输出为:
类型为:__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
通过clang指令生成C++代码后查看isa指针的类型,发现生成了三个block(__main_block_impl_0, __main_block_impl_1, __main_block_impl_2)
但三个block的isa指针都是_NSConcreteStackBlock
,但为什么通过[block class];
查看类型会发生变化呢?
首先,我们一切以运行时的结果为准,编译完是这样子,但是真正在运行过程中,可能会通过runtime动态修改一些东西,这也会导致程序运行过程中结果与编译其实是有点变化的
总结
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- NSGlobalBlock ( _NSConcreteGlobalBlock )
- NSStackBlock ( _NSConcreteStackBlock )
- NSMallocBlock ( _NSConcreteMallocBlock )
在MRC
环境下如果创建这样一个block
void (^block)(void);
void test2()
{
// NSStackBlock
int age = 10;
block = [^{
NSLog(@"block---------%d", age);
}];
}
// 最终打印结果不是 10
这样一个block因为它访问了auto变量,所以是一个栈block
,函数调用完毕出了作用域,那么栈内存的数据就会被释放,最终打印出来的数据就会错乱
解决办法就是将block进行copy操作,那么block就会变成堆block
, 堆上面的空间就需要程序员自己去处理
block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时, 如调用数组的遍历方法
- block作为GCD API的方法参数时
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
对象类型的auto变量
当block内部访问了对象类型的auto变量时会发生什么
- 如果block是在栈上,将不会对auto变量产生强引用
- 如果block被拷贝到堆上
- 会调用
block
内部的copy
函数 - 会调用
block
内部的copy
函数 copy
函数内部会调用_Block_object_assign
函数_Block_object_assign
函数会根据auto
变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- 会调用
- 如果block从堆上移除
- 会调用
block
内部的dispose
函数 dispose
函数内部会调用_Block_object_dispose
函数_Block_object_dispose
函数会自动释放引用的auto
变量(release)
- 会调用
函数 | 调用时机 |
---|---|
copy函数 | 栈上的block复制到堆上时 |
dispose函数 | 堆上的block被废弃时 |
在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
block修改变量
当我们定义一个block时,并不能直接修改auto声明的变量值,如:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
typedef void (^MyBlock)(void);
MyBlock block1 = ^{
age = 20;
NSLog(@"age is %d", age);
};
block1();
}
return 0;
}
其实不能改的本质原因是,age10是在main函数里面声明的,而block要执行的代码是在__main_block_func_0函数里面,__main_block_func_0函数拿到的age变量只是block的age成员,要从__main_block_func_0函数直接修改main函数声明的变量是不可取的
要想改,可以采用static声明、或者是全局变量、或者__block的方式
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
typedef void (^MyBlock)(void);
MyBlock block1 = ^{
age = 20;
NSLog(@"age is %d", age);
};
block1();
}
return 0;
}
而且这种做法并没有修改age的访问方式,还是一个auto声明的变量
__block修饰符
通过clang指令生成C++代码后,发现__main_block_impl_0
里面生成了 __Block_byref_age_0 *age; // by ref
再查看__Block_byref_age_0 *age
定义
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // __forwarding:指向自己
int __flags;
int __size;
int age;
};
查看main函数调用代码:
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
// 精简之后变为
__Block_byref_age_0 age = {
0,
&age,
0,
sizeof(__Block_byref_age_0),
10
}
block的执行函数生成的C++代码: 查看main函数调用代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
NSObject *p = __cself->p; // bound by copy
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
}
利用age拿到__forwarding指针,在通过__forwarding指针访问age,然后直接修改值
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
block的内存管理
当block在栈上时,并不会对__block拥有的变量产生强引用
当block被copy到堆时
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block拥有的变量形成强引用(retain)
当block从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)
本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.