Block
block写法
block的写法:
类型:
返回值(^block的名称)(block的参数)
值:
^(参数列表) {
// 执行的代码
};
block基本使用
- 作用就是用来保存一段代码块,在一个方法定义,在另外一个方法去调用(用的少,可以用定义方法替代,一般在 在一个类中定义,在另外一个类中去调用 用的比较多
定义的几种方式
#方式一: 等号 = ^(参数){ block代码块 }
void(^block1)(int) = ^(int a){
NSLog(@"调用了block%d",a);
};
- 注意点:如果block带有参数,在定义的时候,一定要有参数,并且变量名不能省略
# 方式二: 如果没有参数,参数可以省略
void(^block2)() = ^{
};
# 方式三: 等会右边 = ^返回值(参数),返回值可以省略,不管有没有返回值,都可以省略
int(^block3)() = ^int{
return 1;
};
- 注意:如果Block有返回值,block代码块必须要有返回值
对于初学者,block的语法格式可能比较难记,可以尝试先用快捷的方式敲出格式,再根据生成的格式填入
block快捷方式:inline
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>)
{
<#statements#>
};
block注意事项
- 在block内部可以访问block外部的变量
int a = 10;
void (^myBlock)() = ^{
NSLog(@"a = %i", a);
} myBlock();
输出结果: 10
- block内部也可以定义和block外部的同名的变量(局部变量),此时局部变量会暂时屏蔽外部
int a = 10;
void (^myBlock)() = ^{
int a = 50;
NSLog(@"a = %i", a);
} myBlock();
输出结果: 50
- 默认情况下, Block内部不能修改外面的局部变量
int b = 5;
void (^myBlock)() = ^{
b = 20;
// 报错 NSLog(@"b = %i", b);
};
myBlock();
- Block内部可以修改使用__block修饰的局部变量
__block int b = 5;
void (^myBlock)() = ^{
b = 20;
NSLog(@"b = %i", b);
};
myBlock();
输出结果: 20
-
block中可以访问外面的变量
-
block中可以定义和外界同名的变量, 并且如果在block中定义了和外界同名的变量, 在block中访问的是block中的变量
-
默认情况下, 不可以在block中修改外界变量的值,因为block中的变量和外界的变量并不是同一个变量
-
如果block中访问到了外界的变量, block会将外界的变量拷贝一份到堆内存中,因为block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值
-
如果想在block中修改外界变量的值, 必须在外界变量前面加上__block ,那么会影响到外界变量的值
-
如果没有添加__block是值传递
-
如果加上__block之后就是地址传递, 所以可以在block中修改外界变量的值
下面我们通过一个小案例来演示block的基本使用场景
block保存代码:
案例:点击cell,作出相应的动作,在cell里面利用block保存代码
- 1.在cell模型声明block属性
@property (nonatomic, strong) void(^block)();
- 2.在模型里面保存代码
// 打电话
CellItem *item = [CellItem cellItem];
item.title = @"打电话";
// 定义block给它
item.block = ^{
NSLog(@"打电话");
};
- 3.点击cell调用block
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CellItem *item = self.items[indexPath.row];
if (item.block) {
item.block();
}
}
block传值
开发中,block传值大致分为顺传和逆传
顺传:比如A控制器把值传到Push之后的B控制器,就是顺传(定义属性)
- 很简单,你一般只需要在在B控制器的头文件声明一个属性,A控制器拿到B控制器之后,把属性赋值,B控制器就可以拿到A传过来的值使用.
逆传:从名字就可以看出,是跟顺传反过来的,就是由B控制器反过来传回去A控制器,那么,这就没那么简单了
- 逆传我们可以使用代理,但是开发中一般不会使用了,因为实在是比较麻烦,而且代码量很多,我们一般会使用block来代替代理,从而精简代码,看起来也比较容易理解
先总结一下:传值万能步骤:A传值B,A拿到B 就能传值 B传值A,拿到B就能传值
利用block逆传
- 1.在传递方头文件声明block属性
@property(nonatomic,strong)void(^block)(NSString*value);
- 2.在.m文件触发事件传值(参数)
判断一下是否有block
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 传值:
if (_block) {
_block(@"123");
}
- 3.接收方拿到传递方,拿到block接收值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
ModalViewController *modalVc = [[ModalViewController alloc] init];
modalVc.block = ^(NSString *value){
NSLog(@"利用了block,接收到值%@",value);
};
#好,上面简单介绍了block得传值方式,基本上开发的简单传值就是这么用的,一些复制的传值也是万变不离其宗
block内存管理
写在前面:为什么要理解或者掌握内存?!
#######有一位大牛说过,你要灵活运用一个东西,就必须要知道它的内存是怎样运作,是怎么管理的,才能做到灵活运用到实际开发中
我们都知道,说到内存管理,可以分为ARC和MRC两种情况
先来补充一下知识:
如何判断当前项目是ARC,还是非ARC
1.在ARC中,不允许调用retain,release,retainCount
2.重写dealloc,ARC中不能调用[super dealloc]
怎么进入非ARC环境 : 点击工程文件 -> buildSetting -> ARC
1.ARC的block内存管理
ARC下,默认一个局部变量就是一个强指针,防止一创建就释放
block访问了一个局部变量,就会放到堆里面
* 只要访问了一个外部变量,,生命周期不是全局的,只会放在堆里面
* 只要访问了一个外部变量,,生命周期是全局的,只会放在全局区
只要在block代码块里面使用了self强引用就会导致循环引用
# 注意:只要在block的代码中,访问外部强指针对象,就会把对象强引用
- (void)viewDidLoad {
[super viewDidLoad];
int a = 2;
#注意:只要在block的代码中,访问外部强指针对象,就会把对象强引用
#解决循环引用:将该对象使用weak或者block修饰符修饰之后再在block中使用/或者将其中一方强制制空 xxx = nil 。
__weak typeof(self) weakSelf = self;
_block = ^{
// 定义强指针的self
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf);
});
NSLog(@"%d",a);
};
_block();
}
从上面我们需要知道的是,什么是值传递,什么是指针传递
-
值传递: 访问局部变量
-
指针传递:访问生命周期全局变量
-
而被__block修饰的局部变量,就是指针传递
-
那么问题来了block是存储在堆中还是栈中?
//默认情况下block存储在栈中,如果对block进行一个copy操作, block会转移到堆中
//如果block在栈中, block中访问了外界的对象,那么不会对对象进行retain操作
//但是如果block在堆中, block中访问了外界的对象,那么会对外界的对象进行一次retain
//如果在block中访问了外界的对象,一定要给对象加上__block,只要加上了__block,哪怕block在堆中,也不会对外界的对象进行retain
//如果是在ARC开发中就需要在前面加上__weak
好,知道了上面的知识,你就会明白这一段代码的意思,为什么要加上这一句__strong typeof(self) strongSelf = weakSelf;
我们的目的是:在要执行完那个延迟block,而且拿到对象strongSelf
做完我们需要做的事情之后,modal出来的控制器才销毁
如果没有一个强引用(上面那一行代码),那么,当控制器dismiss之后就释放掉那我们就拿不到weakSelf
这个对象做事情了.
2.非ARC(MRC)的block内存管理
只要block访问了外部变量,生命周期不是全局的(是否在代码块里面),就存放在栈里面
生命周期是全局的,block就存放在全局区
block在非ARC中必须要使用copy
在非ARC环境下,retain相当于strong,为什么block不用retain
因为在非arc中,是不会存放到堆里面,过了代码块就会被释放.再访问就会坏内存访问报错
在非ARC开发注意点:访问属性,一定要使用get,set方法,不能直接使用下划线
bock开发场景:参数使用
-
1.封装一个类的时候,有些时候,怎么去做由外界决定,但是由内部决定什么时候调用,把block当做一个参数去使用.
-
2.动画block:做什么样的动画由我们决定,但是什么时候调用由系统决定.
bock开发场景:作为返回值使用
这里就涉及到链式编程思想了,所谓的链式编程思想: 把所有的方法调用全部通过.语法连接起来,好处:可读性非常好
mgr.add(5).add(5);
其实是分两步走,先调用mgr.add的getter,返回一个block.再跟着()实现这个block
- (CalculatorManager *(^)(int))add;
- (CalculatorManager *(^)(int))add
{
return ^(int value){
_result += value;
return self;
};
}
block在实际开发的应用举个例
- 定义网络请求的类
@interface HttpTool : NSObject
- (void)loadRequest:(void (^)())callBackBlock;
@end
@implementation HttpTool
- (void)loadRequest:(void (^)())callBackBlock
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"加载网络数据:%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
callBackBlock();
});
});
}
@end
- 进行网络请求,请求到数据后利用block进行回调
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.httpTool loadRequest:^{
NSLog(@"主线程中,将数据回调.%@", [NSThread currentThread]);
}];
}
一些事:
-
用strong还是copy的问题?
block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.
★建议:在ARC中,能用strong就用strong,因为copy内部会做很多事情 -
block是对象吗?
是的!苹果告诉我的^0^!文档说的很清楚的
写在最后,block之强大可谓开发利器
------make by LJW 转载请注明出处-------