写在前面:demo 或者代码 评论发邮箱地址,我会及时给需要的小伙伴发过去.
一 : 科普一分钟
- 什么是block: 个人简单的理解为就是一个存放代码片段的容器,作用就是保存代码.
- block 苹果官方定义为
对象
可以用数组
和字典
进行操作,写到这同学们可能会明白了,block 说的简单点就是 一个可以存放代码片段的对象,可以进行内存管理,可以作为属性,等普通对象操作.
既然分析明白了就可以做事情了,接下来进入block 的神器世界
二 : block的基本使用
block的声明
void(^block)();
声明一个名字为 block 的 block 对象;当然 名字可以换啊 我们可以用doit 代替,更形象
void(^doit)();
声明了名字 为doit 的block 代码块对象block 的定义
block 的定义方式通常有三种方式
- 第一种 :没有参数 没有返回值 = 右边相当对名字为 doit1这个block对象的代码块的赋值 里面存放一段代码
void(^doit1)() = ^(){
NSLog(@"关注我吧,会有更多精彩");
};
- 第二种 :如果没有参数,参数可以隐藏
void(^block2)() = ^{
NSLog(@"关注我吧,会有更多精彩");
};
如果有参数,定义的时候 必须要写参数 而且必须要有参数变量名
void(^block22)(int) = ^(int a){
NSLog(@"关注我吧,会有更多精彩");
};
- 第三种 block 返回值可以省略,不管有没有返回值 都可以省略
int(^block3)() = ^int{
return 3;
};
也可以写成
int(^block3)() = ^{
return 3;
};
- block 的类型
int(^)(NSString *)
我们定义了一个返回值 为int
参数为NSString
block 代码块对象类型 它现在还没有名字 我们现在给它取名为 :doit4
int(^doit4)(NSString *) = ^(NSString *name){
return 2;
};
- block 调用
不要以为定义完成 就大功告成了 ,因为我们还没有去调用它,我们写block 代码块的 目的 就是去使用它 .
doit1();
之前我们已经声明了 doit1 这个block 代码块对象 所以运行后的结果是
NSLog(@"关注我吧,会有更多精彩");
- block 的快捷方式
我们手动去写block 会很麻烦 我们可以敲inline
然后选择第一个 接下来 就可以方便我们使用了,会自动联想代码
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
- block作为属性
block 对象也可以当成属性使用 属性的书写原则是 如何声明 就如何属性 block
例如
@property(nonatomic,strong) void(^doit)() ;
一个无返回值,无参数的block类型 名字为doit的属性对象
当然这样书写 我们会不习惯 我们还可以重新定义类型 来写成我们熟悉的形式.
例如重新声明类型
//BlockType;类型的别名
typedef void(^BlockType)();
注意 这个BlockType 是类型名
并不是对象名
所以我们要再定义一个 类型 为BlockType 名字为doit 的对象
@property(nonatomic,strong) BlockType doit;
- blcok 内部变量传递分析
- 看代码分析结果:
- (void)viewDidLoad {
[super viewDidLoad];
int a = 3;
a = 5;
void(^block)() = ^{
NSLog(@"-- %d",a);
};
block();
}
结果为 : 5
- 看代码分析结果
- (void)viewDidLoad {
[super viewDidLoad];
int a = 3;
void(^block)() = ^{
NSLog(@"-- %d",a);
};
a = 5;
block();
}
结果为 : 3
原因分析
如果是局部变量 block 是值传递,当定义block 是 a = 3 此时,block 内部代码块的a = 3 ,改变a = 5,但是不影响block 代码块内容.看代码分析结果
- (void)viewDidLoad {
[super viewDidLoad];
static int a = 3;
void(^block)() = ^{
NSLog(@"-- %d",a);
};
a = 5;
block();
}
- 结果 : 5
- 原因分析 : 如果是静态变量,全局变量,block 是指针传递
三 : block 在开发中的基本应用
1. block 保存一段代码
实例场景:在我们写tableView列表中 根据不同的cell 判断不同点击事件 我们多数人的做法是 通过很多很麻烦的判断如下:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row == 0) {
}else if (indexPath.row == 1){
}else if (indexPath.row == 2){
}
}
其实我们可以用Block 存储要做的事情 来代替这些复杂的判断,因为这样增加了代码的可读性,而且需求发生变化的时候也非常容易的完成了需求的变化
做法:
- 我们在
cellItem
这个模型里添加 block 属性
//保存每个cell 做的事情
@property(nonatomic,strong) void(^block)();
- 保存每个模型对应cell 要做的事情
cellItem *item1 = [cellItem itemWithTitle:@"打电话"];
item1.block = ^{
NSLog(@"睡觉");
};
cellItem *item2 = [cellItem itemWithTitle:@"发短信"];
item2.block = ^{
NSLog(@"吃饭");
};
cellItem *item3 = [cellItem itemWithTitle:@"发邮件"];
item3.block = ^{
NSLog(@"装逼");
};
_items = @[item1,item2,item3];
- 替换复杂又low 的判断
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
cellItem *item = self.items[indexPath.row];
if (item.block) {
item.block();
}
}
2. 逆向传值
我们的经典逆向传值的例子就是代理了吧,大多数的开发者也是习惯于代理的方式,但是block 的传值方式要比代理好太多了,代理的6步骤比较复杂. 接下来我们要用block 替换代理
实例场景:
首先我们定义了两个控制器ViewController 和 TZpopViewController 点击ViewController 的view模态到TZpopViewController 然后 点击TZpopViewController的view 返回 并且传至到ViewController
ViewController方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
TZpopViewController *vc = [[TZpopViewController alloc]init];
vc.block = ^(NSString *index) {
NSLog(@"index = %@",index);
};
vc.view.backgroundColor = [UIColor brownColor];
[self presentViewController:vc animated:YES completion:nil];
}
TZpopViewController 属性
@property(nonatomic,strong)void(^block)(NSString*index) ;
TZpopViewController 方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (_block) {
_block(@"tz123");
}
}
- 分析
首先当我们在ViewController
点击触发touchesBegan
方法时候 创建了 TZpopViewController 并且给它的block
属性赋值了一段带代码
= ^(NSString *index) {
NSLog(@"index = %@",index);
};
此时这个代码片段是不走的 ,因为没有操作执行block
当我们触发TZpopViewController
中的 touchesBegan
方法时候 做了执行block 的方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (_block) {
_block(@"tz123");
}
}
此时则会在ViewController
中走打印方法 打印结果为tz123
四 : block 内存管理
- block 在MRC下的注意事项
由于MRC没有strong ,weak局部对象相当于基本类型,存放于栈区,代码走过就销毁
1.在MRC 中block只能使用copy 修饰,不能使用retain,因为使用retain 了block 存放于栈区,被销毁了.
2.在MRC只要block引用了外部局部变量,block 放在栈里面,只要block 没有引用外部局部变量,block 放在全局区里面.
- block在ARC下的注意事项
1.只要block 引用外部局部变量,block放在堆里面
注意:在ARC
block
使用 strong
修饰,最好不用使用 copy
节省性能.
有的小伙伴会反驳说用copy ,其实是看需求而定 ,因为我们大部分需求是不可变赋值给不可变 ,所以这里建议用strong .如果保守的话也可以用copy
- 分析
我们使用 copy 通常是浅拷贝 因为 是不可变赋值给不可变 ,不用考虑 重新分配内存的问题, 我们copy 修饰 内部方法会走[block copy]
这个方法系统会分析是否从新分配内存 ,浪费资源.所以我们通常情况下使用strong
五 : block 的循环引用问题
- 我们在使用的时候稍微疏忽会造成循环引用,双方都不会销毁,导致内存泄漏.
- 模拟循环引用
在ViewController 定义属性
@property(nonatomic,strong)void(^block1)();
- (void)viewDidLoad {
[super viewDidLoad];
_block1 = ^{
NSLog(@"关注我有更多精彩%@",self);
};
_block1();
}
这种写法就会造成循环引用.
-
分析
block造成循环引用:block 会对里面所有的强指针变量都强引用一次
解决办法
__weak typeof (self) weakself = self;
_block1 = ^{
NSLog(@"关注我有更多精彩%@",weakself);
};
_block1();
}
思考
假如我们想在block 代码块种写一段延迟做的事情怎么办
通常block 代码块走过,则会销毁拿不到.解决办法
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof (self) weakself = self;
_block1 = ^{
__strong typeof (weakself) strongself = weakself;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"关注我有更多精彩%@",strongself);
});
};
_block1();
}
再次强引用一次,知道 延迟代码走完,则指针释放 .
六 : block 在开发中的高级应用
- block 当参数在开发中的应用
1.什么情况下block 被当成参数了呢,
看参数有没有^ 如果有^ 怎参数为block.
2.什么时候需要把block 当成参数,
做的事情由外部决定,什么时候做由内部决定.
代码举例:
- 做一个计算器:要求怎么计算由外部决定,时候时候计算由内部决定
TZcaculoterManager
@property(nonatomic,assign)NSInteger result;
//计算
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock;
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock{
_result = cacultorblock(_result);
}
调用
- (void)viewDidLoad {
[super viewDidLoad];
caculoterManager *magr = [[caculoterManager alloc]init];
[magr cacutor:^(NSInteger result){
result += 5;
result += 7;
return result;
}];
}
- 分析 表面看有点难理解 其实我们不妨把它当成另一个函数
-(void)magrCautor:(NSString*)tz{
}
感觉是一个意思,后者传递的是字符串,前者传递的是block 代码块,区别在于前者控制 传递的代码块何时调用.AFN 封装 等各种封装 很多采用这种
- block 当返回值在开发中的应用
主要应用场景:链式编程,Masonry就是最典型的链式编程,其内部原理用就是用block实现
链式编程特点:把所有的语句 用.号连接起来,好处:可读性非常号.
代码实现 我们先简单写一个返回值为block 的函数
-(void(^)())test{
return ^{
};
}
这个是一个返回值 为无返回值,无参数的block
现在我们来调用这个函数
self.test();
相当于 self.test 这个函数给我们返回block
然后我们去执行 block()
- 操作:我们现在还做 一个需求 封装一个计算器,提供一个累加方法
cacutotermanager
.h
@property(nonatomic,assign)int result;
-(cacutotermanager* (^)(int))add;
.m
-(cacutotermanager* (^)(int))add{
return ^(int value){
_result += value;
return self;
};
}
调用
cacutotermanager *magr1 = [[cacutotermanager alloc]init];
magr1.add(1).add(2).add(4);
模仿了 Masonry
的链式编程方法.
- 解析
magr1.add
调用方法后 返回block 执行block(1)
;
此时block 的返回类型是cacutotermanager
magr
再调用magr(2)....
七 : 总结
通过上述大家对block 的应用应该了解的非常透彻了,希望大家活学活用. 根据需求选择正确的方法.效率更高. ^ _ ^ 下期再见. 想听什么可以在评论区留言. H5,Java 陆续会开始. 加油!!