iOS原理(五)----block
iOS原理(五)----block
block的本质
block本质上也是一个OC对象,它内部也有个isa指针, block是封装了函数调用以及函数调用环境的OC对象.
下面是简单的一个block代码:
typedef void(^MyBlock)(void);
MyBlock block = ^{
NSLog(@"---block---");
};
block();
查看其编译生成的c++代码如下:
typedef void(*MyBlock)(void);
MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g6_f7y9s65s36vcgjz8dy2t0yr00000gn_T_main_6a9ca9_mi_0);
}
// block结构体
struct __main_block_impl_0 {
// block实现
struct __block_impl impl;
// block描述信息
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;
}
};
// block实现结构体
struct __block_impl {
// isa指针,指向block类型
void *isa;
int Flags;
int Reserved;
// block执行逻辑的函数指针
void *FuncPtr;
};
// block描述信息
static struct __main_block_desc_0 {
size_t reserved;
// block的大小
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
从上面的源码可以看出:block其实就是一个带isa指针的结构体,结构体有两个成员变量:block实现的struct __block_impl impl
和block信息的struct __main_block_desc_0* Desc
,其中impl
也是一个结构体,其成员变量有isa指针void *isa
,int Flags
,int Reserved
和函数实现的函数指针oid *FuncPtr
.Desc
也是一个结构体,含有两个成员变量size_t reserved
和block大小的size_t Block_size
,所有block的结构如下:
block对变量的捕获
1.对局部auto变量的捕获
代码如下:
typedef void(^MyBlock)(void);
int age = 10;
MyBlock block = ^{
NSLog(@"---block---%d",age);
};
block();
c++编译代码如下:
Snip20181112_18.png Snip20181112_19.png Snip20181112_20.png可以出__main_block_impl_0
结果体多了个age
成员变量,且以值传递的方式.
2.对局部非auto变量的捕获
代码如下:
typedef void(^MyBlock)(void);
static int age = 10;
MyBlock block = ^{
NSLog(@"---block---%d",age);
};
block();
c++编译代码如下:
Snip20181112_21.png Snip20181112_22.png Snip20181112_23.png可以出__main_block_impl_0
结果体多了个*age
成员变量,且以指针传递的方式.
3.对全局变量的捕获
代码如下:
int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
typedef void(^MyBlock)(void);
MyBlock block = ^{
NSLog(@"---block---%d",age);
};
block();
}
return 0;
}
c++编译代码如下:
Snip20181112_25.png可以出__main_block_impl_0
并没有对全局变量age
捕获,这是因为既然age
是全局变量,也没有必要去捕获它,__main_block_func_0()
函数能直接访问全局变量age
.
block的类型
block类型分为三种:全局类型的_NSConcreteGlobalBlock
,栈类型的_NSConcreteStackBlock
和堆类型的_NSConcreteMallocBlock
,即他们分别在内存中的数据区,堆区和栈区.
1._NSConcreteGlobalBlock
没有访问auto类型变量的block即为_NSConcreteGlobalBlock
.代码如下:
typedef void(^MyBlock)(void);
MyBlock block = ^{
NSLog(@"---block---");
};
block();
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
打印如下:
Snip20181112_27.png可以出此时的类型是_NSConcreteGlobalBlock
,继承于NSBlock
和NSObject
类型.
2._NSConcreteStackBlock
访问了auto类型的block即为_NSConcreteStackBlock
.代码如下:
typedef void(^MyBlock)(void);
int age = 10;
MyBlock block = ^{
NSLog(@"---block---%d",age);
};
block();
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
打印如下:
Snip20181112_29.png可以出此时的类型是_NSConcreteStackBlock
,继承于NSBlock
和NSObject
类型.
3._NSConcreteMallocBlock
对_NSConcreteStackBlock
类型的block进行copy后类型即为_NSConcreteMallocBlock
,代码如下:
typedef void(^MyBlock)(void);
int age = 10;
MyBlock block = [^{
NSLog(@"---block---%d",age);
} copy];
block();
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
打印如下:
Snip20181112_31.png可以出此时的类型是_NSConcreteMallocBlock
,继承于NSBlock
和NSObject
类型.
block的copy
1.如果对_NSConcreteGlobalBlock
类型的进行copy,类型还是_NSConcreteGlobalBlock
,代码如下:
typedef void(^MyBlock)(void);
MyBlock block = [^{
NSLog(@"---block---");
} copy];
block();
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
打印如下:
Snip20181112_32.png2.如果已经在堆上的_NSConcreteMallocBlock
类型的block进行copy,该block的应用计数会加1.
3.如果在栈上的_NSConcreteStackBlock
类型的block进行copy,该类型为_NSConcreteMallocBlock
.
4.在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上:
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
5 MRC下block属性的建议写法:@property (copy, nonatomic) void (^block)(void);
6 ARC下block属性的建议写法:@property (strong, nonatomic) void (^block)(void);
和@property (copy, nonatomic) void (^block)(void);
都可以.
auto类型的对象变量
当block内部访问了外部auto类型的对象变量时:
1.当block在栈上时:将不会对auto变量产生强引用.
2.当block被拷贝到堆上:会调用block内部的copy函数, copy函数内部会调用_Block_object_assign
函数,_Block_object_assign
函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用.
3.当block从堆上移除:会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose
函数, _Block_object_dispose
函数会自动释放引用的auto变量(release).
__block
修饰符
__block
可以用于解决block内部无法修改auto变量值的问题, __block
不能修饰全局变量、静态变量(static).代码如下:
typedef void(^MyBlock)(void);
__block int age = 10;
MyBlock block = ^{
age = 20;
NSLog(@"---block---%d", age);
};
block();
生成的c++的代码为:
typedef void(*MyBlock)(void);
// 生成一个新的对象age(结构体),类型为__Block_byref_age_0
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
// block定义,就是指向__main_block_impl_0的指针
MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// block逻辑执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g6_f7y9s65s36vcgjz8dy2t0yr00000gn_T_main_eb767f_mi_0, (age->__forwarding->age));
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 指向__Block_byref_age_0的指针
__Block_byref_age_0 *age; // by ref
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
新生成包装age的结构体
struct __Block_byref_age_0 {
void *__isa;
// 指向自己类型的指针
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
// 原本的age变量
int age;
};
// block描述
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};
从上面的编译的c++的源码得知:__block修饰的age变量变成了一个对象,类型为struct __Block_byref_age_0
结构体.
__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)
__block的__forwarding指针
已知由__block修饰的变量生成的对象中有__Block_byref_age_0 *__forwarding
成员变量,在栈上的block__forwarding
指向自己,当被copy之后,栈上的block的__forwarding
指向堆上的block,堆上的__forwarding
指向自己.
__block修饰对象
- 当__block变量在栈上时,不会对指向的对象产生强引用.
- 当__block变量被copy到堆时,会调用__block变量内部的copy函数, copy函数内部会调用
_Block_object_assign
函数,_Block_object_assign
函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain). - 如果__block变量从堆上移除,会调用__block变量内部的dispose函数, dispose函数内部会调用
_Block_object_dispose
函数,_Block_object_dispose
函数会自动释放指向的对象(release).
循环引用问题
在Animal
定义一个block,实现并调用block:
typedef void(^MyBlock)(void);
@interface Animal : NSObject
@property (nonatomic,copy) MyBlock block;
@end
@implementation Animal
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
Animal *ani = [[Animal alloc] init];
ani.block = ^{
NSLog(@"%@",ani);
};
ani.block();
运行代码,并没有回收ani
对象,ani
强引用了block
,而block
内部强引用了ani
,导致对象得不到及时释放.
我们可以用__weak
(对象释放后为nil),__unsafe_unretained
(对象释放后指向原来的内存).
Animal *ani = [[Animal alloc] init];
// 或者__unsafe_unretained
__weak typeof(ani) weakAni = ani;
ani.block = ^{
NSLog(@"%@",weakAni);
};
ani.block();
还可以用__block解决(必须要调用block).
Animal *ani = [[Animal alloc] init];
__block typeof(ani) weakAni = ani;
ani.block = ^{
NSLog(@"%@",weakAni);
weakAni = nil;
};
ani.block();
Snip20181112_37.png
前面都是ARC情况下,在MRC情况下,只能用__unsafe_unretained
和__block,此时用__block解决循环引用时,不需调用block,也不必将对象置为nil,这是因为在MRC下,__block下生成的对象,对变量时弱引用.
Animal *ani = [[Animal alloc] init];
__block typeof(ani) weakAni = ani;
ani.block = ^{
NSLog(@"%@",weakAni);
};
[ani release];