一、Block的本质
block本质上也是一个OC对象,它的内部也会有一个isa指针,它是封装了函数调用以及函数调用环境的OC对象。
比如,定义如下一个block
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!");
};
这段block它的底层源码如下:
首先上面代码库中的block在内存中是一个叫__main_block_impl_0的结构体,结构体中有两个结构体变量,一个是叫impl的变量,一个叫Desc的指针。
在impl中我们可以看到它的内部有一个isa指针,所以说我们能确定它是一个OC对象。且在impl中有一个叫funcPtr的指针,它指向的是block执行的代码块(函数)的内存地址。Desc中存放的就是block内存大小等信息。
二、block的auto值捕获
1、如下代码打印结果是多少?
int age = 10;
void (^block)(void) = ^{
NSLog(@"age", age);
};
age = 20;
block();
打印结果:10
原因:由于之前的知识点,我们知道block在创建的时候会生成一个impl和Desc属性。当block中含有其它变量的时候还会生成对应的变量age,所以它就捕获到了age的值为10,之后 age = 20这句代码只是修改了age的值,由于是值传递方式,并不能改变block中age的值,所以打印结果为10。
2、如下代码打印结果是多少?
/*auto:自动变量,离开作用域就会销毁。
它是默认的关键字,所以通常情况下是省略的。*/
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20;
block();
打印结果:age is 10, height is 20。
如图,block捕获到的auto变量为普通变量,捕获到的static变量为指针,由于指针所指向的是变量的内存地址,当height = 20时,内存中的值也变成了20。
三、Block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
1.
void (^block1)(void) = ^{
NSLog(@"block");
}
NSLog(@"%@",[block1 class]) //__NSGlobalBlock__
2.
static int age = 10
void (^block1)(void) = ^{
NSLog(@"%d",age);
}
NSLog(@"%@",[block1 class]) //__NSGlobalBlock__
2.NSStackBlock:访问auto类型变量的block类型。它存储在栈区。
PS:栈区的数据在调用之后会自动销毁。
3.
int age = 10
void (^block1)(void) = ^{
NSLog(@"%d",age);
}
NSLog(@"%@",[block1 class]) //__NSStackBlock__
4.
void (^block)(void)
void test( ) {
int age = 10
block = ^{
NSLog(@"block----%d",age);
}
}
main(){
test( );
block( ); //打印结果为:block----272632488
}
/*由于block是栈类型的,执行test方法之后,block内部的age变量被销毁,再调用block方法就会打印出一个脏数据*/
3.NSMallocBlock:对stack类型的block进行copy操作,得出的类型为Malloc类型的block,也就是堆类型的block变量。堆数据需要手动管理内存,需要手动释放。
四、Block的copy
1、在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时,如数组中的遍历方法:enumerateObjectsUsingBlock:
- block作为GCD API的方法参数时,如dispatch_once(&onceToken,^{})
2、ARC下block属性的写法:
@property(strong, nonatomic) void(^block)(void);
@property(copy, nonatomic) void(^block)(void);
3、MRC下block属性的写法:
@property(copy, nonatomic) void(^block)(void);
五、对象类型的auto变量
之前我们所说的auto类型变量讲的是基本数据类型,如int age = 10。当我们遇到自定义的对象类型,那么它的auto变量在block中是怎么处理呢?
6.
void (^Block)(void)
Person * person = [[Person alloc]init];
person.age = 10
Block block = ^{
NSLog(@"block----%d",person.age);
} ;
}
/*因为Person类是局部变量,创建block的时候将Person捕获,
block内部产生了一个Person * person的指针对象。
所以person对象不会释放,它会等block释放时再进行释放*/
结论:当block内部访问了对象类型的auto变量时:
1、如果block是在栈上,不管是使用强指针还是弱指针对auto变量进行访问,都不会对auto变量产生强引用。
2、如果block被拷贝到堆上,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
3、如果block从堆上移除,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)
六、__block修饰符
我们在实际开发过程中,有可能需要暂时修改auto变量的值。此时,如果使用static来修饰或者声明为全局变量的话是会达到修改的目的,但是一经修改就不能恢复。此时用__block修饰变量可以解决这个问题。
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
Block block = ^{
age = 20;
NSLog(@"age is %d", age);
};
block(); //打印结果:age is 20
}
return 0;
}
1、__block可以用于解决block内部无法修改auto变量值的问题
2、__block不能修饰全局变量、静态变量(static)
七、__block的内存管理
1、当block在栈上时,并不会对__block变量产生强引用。
2、当block被copy到堆时,_Block_object_assign函数会对__block变量形成强引用。
3、被__block修饰的对象类型:当__block变量在栈上时,不会对指向的对象产生强引用;当__block变量被copy到堆时,_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)。
八、block的循环引用问题
- 在block内部使用外部指针且会造成循环引用情况下,需要用_weak修饰外部指针:
_weak typeof(self) weakSelf = self; - 在block内部如果调用延时函数还是用弱指针会取不到该指针,因为已经销毁了,需要在block内部再将弱指针重新强引用一下:
__strong typeof(self) strongSelf =weakSelf;
网友评论