一、概述
block
又称为闭包,一个能够访问局部变量的函数。
闭包=一个函数(或指向函数的指针)+该函数执行的外部上下文变量(自由变量)
block:
1、可嵌套定义;
2、可定义在方法的内部或外部;
3、本质是对象,使代码高内聚。
使用clang
命令将OC
代码转换为C++
文件。
命令如下:
clang -rewrite-objc main.m
在同级目录下生产了一个同名的.cpp
文件,打开查看:
open main.cpp
二、block的定义与使用
1、无参数返回值
void (^MyBlockOne)(void) = ^(void){
NSLog(@"无参数,无参数返回值");
};
MyBlockOne();//无参数
2、有参数无返回值 也可写为void (^MyBlockTwo)(int value)
void (^MyBlockTwo)(int) = ^(int value){
NSLog(@"有参数无返回值:%d",value);
};
MyBlockTwo(1);//有参数
3、无参数有返回值
int (^MyblockThree)(void) = ^(void){
return 1;
};
int result = MyblockThree();
NSLog(@"无参数参数有返回值:%d",result);
4、有参数有返回值
int (^MyblockFour)(int ,int) = ^(int a, int b){
return a+b;
};
result = MyblockFour(1,3);
NSLog(@"有参数参数有返回值:%d",result);
5、项目中经常上使用typedef
定义block
定义后可以声明为一个block
属性,copy
修饰,即使非copy
修饰系统也会自动对该属性进行copy
操作
typedef int (^Myblock)(int a,int b);
@property (nonatomic,copy) Myblock block;
self.block = ^int(int a, int b) {
NSLog(@"我是一个typedef定义的block,a=%d,b=%d",a,b);
return a+b;
};
self.block(1,3);
三、block与外界变量
1、默认情况
对block
外的变量引用,默认是将其复制到block
数据结构中实现访问的,没用直接引用变量本身,因此不能修改外部变量。
-(void)blockOne{
int age = 18;
void (^block)(void) = ^(void){
NSLog(@"1、默认情况 age:%d",age);
};
age = 20;
block();//打印结果为18
}
__block修饰外部变量
使用__block
修饰的变量,其实复制了该变量的地址来实现访问的,因此在block
内部或外部修改,该变量都将被修改。
-(void)blockTwo{
__block int age = 18;
void (^block)(void) = ^(void){
NSLog(@"2、__block修饰外部变量 age:%d",age);
};
age = 20;
block();//打印结果为20
}
使用命令将OC
代码转换成C++
文件:
clang -rewrite-objc External.m
查看代码可见blockTwo
方法对应的转换后的C++
代码:
static void _I_External_blockTwo(External * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 18};
void (*block)(void) = ((void (*)())&__External__blockTwo_block_impl_0((void *)__External__blockTwo_block_func_0, &__External__blockTwo_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
(age.__forwarding->age) = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__block int age = 18
转换成一个结构体:
struct __Block_byref_age_0 {
void *__isa;//指向一个存储自身基础信息的地址的指针
__Block_byref_age_0 *__forwarding;//指向自己的指针
int __flags;//标识
int __size;//结构体大小
int age;//变量值
};
对应以上的结构体赋值如下:
__Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
18
};
四、block存储区
block的内存分布有三种类型:
1、全局块(_NSConcreteGlobalBlock)
2、栈块(_NSConcreteStackBlock)
3、堆块(_NSConcreteMallocBlock)
确定block的存储位置:
1、block
不访问外部变量(包括栈和堆中的变量)
block
不在栈中也不在堆中,在代码代码段中,ARC、MRC
下也是如此,此时为全局块。
2、block
访问外界变量
MRC
环境下:block
默认存储在栈区。
ARC
环境下:block
默认存储在堆区,实际放在栈区,ARC
情况下会自动拷贝到堆区,自动释放。
3、block
代码块本身为栈区。
//1、全局区
Myblock block = ^int(int a, int b) {
NSLog(@"我是一个typedef定义的block,a=%d,b=%d",a,b);
return a+b;
};
block(1,3);
NSLog(@"全局区打印:%@",block);//NSGlobalBlock
//2、栈区-代码块即为栈区必须访问外部变量
int a = 10;
NSLog(@"栈区打印:%@",^{
NSLog(@"栈区打印:%d",a);//NSStackBlock
});
//3、堆区
__block int b = 10;
void (^mallocBlock)(void) = ^(void){
b = 1;//此处必须有引用才会被复制到堆区
};
NSLog(@"堆区打印:%@",mallocBlock);//NSMallocBlock
ARC
下,访问外部变量的block
为什么要自动从栈区拷贝到堆区,栈上的block
当作用域结束,block
就被释放,和自动变量一样,__block
变量也同时被废弃,为延长生命周期,需要把block
复制到堆中,复制完成原有block
被释放,保留当前block
块。
五、__block变量与_forwarding
在copy
操作后,栈区__forwarding
,由指向自己变为指向复制后的堆上的结构体。
六、防止block循环引用
1、ARC
下使用__weak
修饰对象
2、MRC
下使用__block
修饰对象
1)__weak
修饰对象解决循环引用
在block
中使用延时操作,self
会在延时操作前释放,因此在使用延时操作时需要对weakSelf
有个强引用才能在延时操作中使用。
-(void)demo1{
self.name = @"hibo";
__weak typeof (self) weakSelf = self;
self.block1 = ^ {
__strong typeof (self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"name:%@",strongSelf.name);
});
NSLog(@"1->%@",weakSelf.name);
};
self.block1();
}
但是出现一个奇怪的问题:
2)传参代替__weak
:
-(void)demo2{
self.name = @"yahibo";
self.block2 = ^ (ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"demo2:%@",vc.name);
});
NSLog(@"demo2->%@",vc.name);
};
self.block2(self);
}
3)__block
应用对象,在block
中置空解决循环引用:
-(void)demo3{
self.name = @"yahibo";
__block SecondController *vcself = self;
self.block3 = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"demo3:%@",vcself.name);
vcself = nil;
});
};
self.block3();
}