鸡汤
“你知道一败涂地有什么好处吗?”
“那就是,你仅剩一事可做:绝地反击”
前言
本人作为一个菜鸟干iOS开发一年左右,除了深感目前该行业的艰难以外,也在慢慢学习当中,今天和大家谈谈关于 我对Block的认识,有什么不足的地方,还望大家多多指正
1.Block的认识
Block实际上是OC语言对闭包的实现,是带有自动变量值的匿名函数
Block的作用: 保存一段代码
闭包:一个函数,或者一个指向函数的指针,加上这个函数执行的非局部变量。通俗来说就是闭包允许一个函数访问声明该函数运行上下文中的变量,甚至可以访问不同运行上文中的变量。
2.Block的基本语法 (声明 -> 实现 -> 调用)
书写Block的快捷键 : inline
- 无参数无返回值
void(^b1)(void); //声明 ^托字符:表示后面的是 block变量
void(^b1)(void) //block的类型
//b1是 block名字,也叫 block 变量
^(void){ }; //block的实现部分
void(^b1)(void) = ^(void){ //声明实现放一起了
NSLog(@"无参无返回值");
// block 的实现部分
};
//调用 block.
b1();
- 无返回值,有参数
//定义block 参数只需要有一个类型,但是实现部分需要有参数名。
void(^b2)(NSString *) = ^(NSString *string){
NSLog(@"%@",[string stringByAppendingString:@"有人说写字难看的男生长得很帅"]);
};
b2(@"照镜子发现,都TM是骗人的"); //调用block
- 有参数,有返回值
// 写一个block实现两个数的 乘积,并返回结果
NSInteger(^b3)(NSInteger,NSInteger) = ^(NSInteger a,NSInteger b){
return a * b;
};
NSInteger result1 = b3(3,5);
NSLog(@"%ld",result1);
- 有返回值,无参数
// 写一个 block实现,返回一个[30,50]的随机数;
int (^b5)(void) = ^(){
int i = arc4random()%21+30;
return i;
};
int i = b5();
NSLog(@"%d",i);
3.Block在开发中的应用场景
- 在一个方法中定义,在另一个方法中调用
- 在一个类中定义,在另一个类中调用 (常用)
声明
//BlockType:类型的别名,这里不是变量名
typedef void(^BlockType)();
@interface ViewController ()
//block怎么声明,就如何定义成属性
@property (nonatomic, strong) void(^block)(); //开发中推荐用这种
//@property (nonatomic, strong) BlockType block; //和上面一样
@end
实现
void(^block)() = ^() {
NSLog(@"这里就是Block块里的内容,注意:只有在调用block的时候这个block块里的内容才会被执行");
};
_block = block;
调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// block的调用:就去寻找保存的代码,直接调用
_block();
}
需求:TableView展示3个cell,打电话,发短信,发邮件
首先我们创建一个模型Cell item 在.h中代码如下
#import <Foundation/Foundation.h>
@interface CellItem : NSObject
//设计模型:控件需要展示什么内容,就定义什么属性
@property (nonatomic, strong) NSString *title;
//保存每个cell做的事情
@property (nonatomic, strong) void(^block)();
+ (instancetype)itemWithTitle:(NSString *)title;
@end
在Cellitem.m中
#import "CellItem.h"
@implementation CellItem
+ (instancetype)itemWithTitle:(NSString *)title{
CellItem *item = [[self alloc] init];
item.title = title;
return item;
}
@end
在ViewController.m中,我们实现需求
#import "TableViewController.h"
#import "CellItem.h"
@interface TableViewController () <UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *items;
@end
@implementation TableViewController
/*
需求:tableView展示3个cell,打电话,发短信,发邮件
*/
- (void)viewDidLoad {
[super viewDidLoad];
_tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:(UITableViewStylePlain)];
[self.view addSubview:_tableView];
_tableView.delegate = self;
_tableView .dataSource = self;
//创建模型
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];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = @"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:cellIdentifier];
}
CellItem *item = self.items[indexPath.row];
cell.textLabel.text = item.title;
// if (indexPath.row == 0) {
// //打电话
// }else if (indexPath.row == 1){
// //发短信
// }else if (indexPath.row == 2){
// //发邮件
// }
return cell;
}
//点击cell的时候就会调用
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//把要做的事情(代码)保存到模型
CellItem *item = self.items[indexPath.row];
if (item.block) {
item.block(); //此处调用了Block,所以才会走block块中保存的代码
}
}
@end
接下来我们看一下Block中的逆向传值
首先我们创建2个控制器, FirstViewController和SecondViewController, 在后者.h中我们实现block的声明代码如下
#import <UIKit/UIKit.h>
typedef void(^passValue)(NSString *str,NSURL *url);
@interface SecondViewController : UIViewController
@property (nonatomic, copy) passValue block; //block作为属性
@end
在SecondViewController.m中实现布局以及在返回上一页的方法中进行block的调用
- (void)leftAction:(UIBarButtonItem *)sender{
[self.navigationController popViewControllerAnimated:YES];
if (self.block) {
self.block(self.secondTF.text,self.url); //调用Block
}
}
在FirstViewController.m的点击跳转的方法中实现Block块的内容
- (void)rightAction:(UIBarButtonItem *)sender{
SecondViewController *secondVC = [SecondViewController new];
[self.navigationController pushViewController:secondVC animated:YES];
__weak FirstViewController *weakSelf = self;
//这个block只有在second中被调用时才会执行,通过回调,将second中的文本框的内容传递给first
secondVC.block = ^(NSString *str,NSURL *url){ //这里面的string就是回调second页面中传递的参数
weakSelf.textField.text = str;
NSData *data = [NSData dataWithContentsOfURL:url];
weakSelf.imageV.image = [UIImage imageWithData:data];
};
}
接下来是Block作为参数的使用
什么时候需要把block当做参数去使用呢? 做什么事情由外界决定,但是什么时候做由内部决定
需求: 封装一个计算器,提供一个计算方法,怎么计算由外界决定,什么时候计算由我内部决定
首先,我们创建一个CacultorManager类,代码如下:
#import <Foundation/Foundation.h>
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;
//计算
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;
@end
#import "CacultorManager.h"
@implementation CacultorManager
- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock{
if (cacultorBlock) {
_result = cacultorBlock(_result);
// 作为方法中的参数,这里开始调用 参数cacultorBlock, 也就是由方法内部来决定的
}
}
@end
在ViewController.m中,我们实现如下代码:
- (void)viewDidLoad {
[super viewDidLoad];
/**
* 怎么区分参数是blcok,就看有没有^,只要有^,把block当做参数就行
* 注意:把block当做参数,并不是马上就去调用Block,什么时候调用,由方法内部决定
*/
//创建计算器管理者
CacultorManager *mgr = [[CacultorManager alloc] init];
//调用了方法,block是参数,不管block内有多少东西,都只作为参数进行了传递,并不一定会执行。调用block才会执行
[mgr cacultor:^(NSInteger result) {
result += 5;
result += 6;
result *= 2;
return result;
//上面的计算器类调用了方法就走方法里面的东西,方法里面又调用block块,就返回到这里走block块中的内容,也就是说具体执行不执行block块中的内容要看调没调用block
}];
NSLog(@"%ld",mgr.result);
}
接下来我们再看一下Block作为方法返回值的用法
这里要提到关于第三方框架Masonry的链式编程思想,即把所有的语句用 点语法 连接起来,这样优点就是可读性非常高,其核心就是返回值是一个block
- (void(^)())test{ //block 作为返回值
NSLog(@"%s",__func__);
return ^{
NSLog(@"调用了blcok");
};
}
----------------------- 分割线 ----------------------
- (void)viewDidLoad {
[super viewDidLoad];
self.test(); //这就是为什么masonry为什么能写出这样的效果,因为返回值是一个blcok
//我们做个比较
void(^block)() = ^{
NSLog(@"调用了block");
};
block(); //这个和上面的 self.test()一样
}
需求: 封装一个计算器,提供一个加号方法
同样的,我们创建一个CalculatorManager类, 在这个类中我们先实现普通的方法,然后和作为返回值的block做一个比较
#import <UIKit/UIKit.h>
@interface CalculatorManager : UIViewController
@property (nonatomic, assign) int result;
- (CalculatorManager *)add:(int)value; //普通方法
- (CalculatorManager * (^)(int))add; // block作为返回值方法
@end
#import "CalculatorManager.h"
@interface CalculatorManager ()
@end
@implementation CalculatorManager
- (CalculatorManager *)add:(int)value{
_result += value;
return self;
}
- (CalculatorManager * (^)(int))add{
return ^(int value){
_result += value;
return self;
};
}
@end
我们在ViewController.m中使用这个类,看看有什么不同点
- (void)viewDidLoad {
[super viewDidLoad];
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr add:5];
[mgr add:5];
[mgr add:5]; //如果这样的话,不太好,会有很多行
NSLog(@"%d",mgr.result);
mgr.add(5);
mgr.add(5).add(6).add(7).add(8).add(9).add(10);
NSLog(@"%d",mgr.result);
}
以上就是一些block的基础使用,接下来我们来看一下使用block过程中的一些注意点
Block的内存管理
在苹果的api文档中,block是一个对象,所以存在着内存管理的方式(MRC/ARC)
在MRC中:
- 只要block没有引用外部的局部变量,block放在全局区
- 只要block引用外部局部变量,block放在栈里面
- block只能使用copy,不能使用retain,如果使用retain,block还是在栈里面(既然是在栈里面,代码块一过,block就会销毁)
在ARC中:
- 只要block没有引用外部的局部变量,block放在全局区
- 只要block引用外部局部变量,block放在堆里面
- blcok使用Strong,最好不要使用copy
Block的循环引用:
- Block造成循环引用:Block会对里面所有强指针变量(外部对象变量)全部强引用一次 所以加 WeakSelf。
- 如果是局部变量,block是值传递
- 如果是静态变量,全局变量,__block修饰的变量,block是指针传递
总结:
- 不能再block中直接修改局部变量,如有需要添加一个** __block** 来修饰,block可以访问全局变量。
- 在block作为函数参数的时候,使用block的时候引用计数会自动+1,所以我们用__weak 去替换原有的 self才行。