block的声明和简单使用
- 苹果官方文档声明,block是objc对象。
block的定义方式
- 无返回值无参数的block
void(^block1)() = ^{
NSLog(@"调用了block1");
};
- 无返回值有参数的block,参数的类型和传入参数的值不能省略,即
int a
不能省略
void(^block2)(int) = ^(int a){
NSLog(@"调用了block2");
};
- 有返回值的block,返回值的类型可以省略,要是有参数,参数的类型和传入参数的值不能省略。
int(^block3)() = ^int{
return 3;
};
block定义总结
总结:block的定义,不管block有没有返回值,block的实现部分都可以省略;若block有参数,则参数类型和传入参数的值不能省略。
- 小tip:若初学者觉得block格式奇怪,记不住,可以输入
inline
就可以出现block,如图:
block使用场景之----代理传值
在开发中控制器逆传传值最常用的就是用代理的方法,然而用block也可以实现逆传传值,而且更加简单方便。
-
场景:假如在窗口的根控制器ViewController中点击屏幕的view,模态弹出一个窗口ModalViewController,再点击ModalViewController的view销毁ModalViewController,同时将想要传的值传给ViewController。
-
在ViewController中的- touchesBegan方法中,进行到ModalViewController的跳转,同时给ModalViewController的block属性声明,注意!这里并没有调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
ModalViewController *modalVc = [[ModalViewController alloc] init];
modalVc.view.backgroundColor = [UIColor brownColor];
modalVc.block = ^(NSString *value) {
NSLog(@"%@",value);
};
// 跳转
[self presentViewController:modalVc animated:YES completion:nil];
}
- 在ModalViewController的.h文件中声明一个block属性,这个block属性没有返回值,接收一个字符串类型的参数
@property (nonatomic, strong) void(^block)(NSString *value);
- 在ModalViewController的.m文件中,点击控制器的view进行block的调用,此时就会将字符串中的值传给ViewController。注意if语句的判断,否则block不存在的时候,会报坏内存访问错误
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (_block) {
_block(@"我是萌萌哒ModalViewController");
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Paste_Image.png
block的内存管理
- block在MRC环境和ARC环境的内存管理不同
MRC
- 首先,在项目的building settings里面将环境改成MRC的环境
- block会根据其内部引用的变量的类型不同而在不同的存储空间
- block可以存在全局区,栈区,堆区(
__NSGlobalBlock__
,__NSStackBlock__
,__NSMallocBlock__
)。- 若block中引用外部的局部变量,则打印结果是
__NSStackBlock__
,说明block存储在栈区
- 若block中引用外部的局部变量,则打印结果是
- (void)viewDidLoad {
[super viewDidLoad];
//局部变量a
int a = 3;
void(^block)() = ^{
NSLog(@"调用block %d",a);
};
NSLog(@"%@",block);
}
Paste_Image.png
- 若block中的变量的全局变量后者静态变量,则打印结果是
__NSGlobalBlock__
,说明block存储在全局区
//全局变量a
int a = 3;
- (void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^{
NSLog(@"调用block %d",a);
};
NSLog(@"%@",block);
}
- (void)viewDidLoad {
[super viewDidLoad];
//静态变量a
static int a = 3;
void(^block)() = ^{
NSLog(@"调用block %d",a);
};
NSLog(@"%@",block);
}
Paste_Image.png
- 那么block什么时候保存在堆区呢?
- 当block属性被copy修饰的时候放在堆区,打印结果为
__NSMallocBlock __
//定义copy修饰的block属性
@property (nonatomic, copy) void(^block)();
- (void)viewDidLoad {
[super viewDidLoad];
int a = 3;
void(^block)() = ^{
NSLog(@"调用block%d",a);
};
self.block = block;
NSLog(@"%@",self.block);
}
- 假如我们尝试用retain修饰block,会报这样的警告,同时运行的结果是保存在栈区。
总结
只要block没有引用外部局部变量,block放在全局区
只要Block引用外部局部变量,block放在栈区.
block只能使用copy,不能使用retain,使用retain,block还是在栈区
ARC
- 若block中的变量的全局变量后者静态变量,则打印结果仍然是
__NSGlobalBlock__
,block仍然存储在全局区
- 若block中引用外部的局部变量,则打印结果是
__NSMallocBlock__
,说明block存储在堆区
,和MRC情况不同。 - 对于ARC下的block属性用strong和copy修饰都是一样的,但是copy的底层会让setter方法多一层对可变不可变属性的判断,浪费资源,所以在ARC的情况下用strong修饰block,同理NSString属性修饰也用strong。
block的内存管理总结
总结:block里面引用全局变量或者静态变量,放在全局区__NSGlobalBlock__
* MRC情况:
* block里面引用局部变量,存放在栈区__NSStackBlock__
* MRC中修饰block只能用copy,不能用retain,因为retain修饰block还在栈中,若block是局部变量,不能包住block的命,再次访问会造成坏内存访问。
* ARC情况:
* block里面引用局部变量,存放在堆区__NSMallocBlock__
* block在ARC情况下用strong修饰
* 对于NSString和block在ARC尽量用strong,因为copy底层的setter方法会对新对象做一次copy操作,还要判断是不是不可变对象,浪费性能
*/
block的循环引用
- block会对内部的所有的强指针变量都强引用一次,这就会造成block的循环引用,造成内存泄露。下面看例子:
在ViewController中以modal的方式展示ModalViewController,在ModalViewController中调用dismiss方法销毁控制器。则在dealloc方法中会打印ModalViewController被销毁。
然而若定义一个block属性,并在block的实现中做如下操作:
@property (nonatomic, strong) void(^block)();
_block = ^{
NSLog(@"%@",self);
};
此时当调用ModalViewController的dismiss方法的时候不会调用dealloc方法中的打印语句,说明ModalViewController没有被真正的销毁。因为ModalViewController强引用一个block属性,block会对内部的强指针self进行一次强引用。所以造成循环引用
- 改进方法是将self改成弱指针,这样就不会造成循环引用
__weak typeof(self) weakSelf = self;
_block = ^{
NSLog(@"%@",weakSelf);
};
block的变量传递
- 请看这样一段代码,大家猜一猜打印的结果是3还是5?
/*************** 1 **************/
- (void)viewDidLoad {
[super viewDidLoad];
//普通局部变量a
int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
如果这样呢?
/*************** 2 **************/
- (void)viewDidLoad {
[super viewDidLoad];
//静态变量a
static int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
这样呢?
/*************** 3 **************/
//全局变量a
int a = 3;
- (void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
这样呢?
/*************** 4 **************/
- (void)viewDidLoad {
[super viewDidLoad];
//用__block修饰的局部变量a
__block int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
实际编译可以得出结果,除了1的打印结果是3,剩下的都是5。那么可以总结出结论如下:
* 如果block中是普通的局部变量,是值传递,即在局部变量声明的那一刻就把局部变量的值放到block中了,之后在修改不会造成影响
* 如果是静态变量,全局变量,__block修饰的变量,block都是指针传递。指针传递的值可以修改。
网友评论