在iOS开发中,Block属于一种比较实用的技巧,它的作用是保存一段代码。Block经常用于传值,或作为参数使用。
Block的基本使用
Block是一种特殊的数据类型,用来保存一段代码。Block类似于匿名函数。
Block声明
// 无参声明
void(^emptyBlock)();
// 有参声明(第二个小括号里写的是参数的类型,多个参数需要逗号隔开)
void(^nonEmptyBlock)(NSString *,NSString *);
- Block定义:3种方式
// 1.在有参数的情况下,=号后面需要列出参数。
void(^defineBlock1)(NSString *) = ^(NSString *str){
NSLog(@"defineBlock1:%@",str);
};
// 2.在无参的情况下,=号后面可以隐藏参数
void(^defineBlock2)() = ^{
NSLog(@"defineBlock2");
};
// 3.=号后面可以列出返回值,也可以隐藏返回值
// defineBlock3的类型:NSString *(^)(NSString *),实际上就是Block的声明去掉Block的名字
NSString *(^defineBlock3)(NSString *) = ^NSString *(NSString *str){
NSLog(@"defineBlock3:%@",str);
return str;
};
- Block调用
defineBlock1(@"我是1");
defineBlock2();
defineBlock3(@"我是3");
- Block快捷方式:输入inlineBlock
// block快捷方式(新手福利): 输入inlineBlock,系统会给出block模板
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
- 定义Block属性:2种方式
// 1.怎么声明Block就怎么定义Block属性;
@property (nonatomic, strong) void(^blockName)();
// 2.定义一个Block类型,然后用这个Block类型声明Block。
// BlockType:Block的类型。
typedef void(^BlockType)();
// blockName:使用Block时使用的名字,相当于方法名……
@property (nonatomic, strong) BlockType blockName;
Block在MRC下的内存管理
只要block没有引用外部局部变量,block放在全局区;只要block引用外部局部变量,block放在栈里面。定义block属性,只能使用copy,不能使用retain;当使用copy时,block在堆里,而使用retain时,block还是在栈里。
Block在ARC下的内存管理
只要block没有引用外部局部变量,block放在全局区;只要block引用外部局部变量,block放在堆里面(这里和MRC下不同,MRC下放在栈里面);block在ARC下使用strong,最好不要使用copy,copy会多做许多事情。
Block循环引用
判断自身是否释放的方法:在-dealloc方法中打印下字符串,看是否会进入该方法。
- (void)dealloc
{
NSLog(@"ViewController已销毁");
}
下面写法会造成循环引用,因为Block会对里面所有强指针变量都强引用一次。
void(^block)() = ^{
NSLog(@"%@",self);
};
block();
__weak typeof(self) weakSelf = self;
_block = ^{
NSLog(@"%@",weakSelf);
};
- Block变量传值
如果是局部变量,Block是值传递
int i = 3;
void(^block)() = ^{
NSLog(@"%d",i);
};
i = 4;
block();
// 打印结果是3
如果是静态变量(static修饰)、全局变量、__block修饰的变量,Block都是引用传递
__block int i = 3;
void(^block)() = ^{
NSLog(@"%d",i);
};
i = 4;
block();
// 打印结果是4
Block使用场景:方法间传值
// 定义block属性
@property (nonatomic, strong) void(^blockName)();
// 两个block方法
- (void)test1
{
self.blockName = ^{
NSLog(@"在一个方法中定义,在另一个方法中使用");
};
}
- (void)test2
{
self.blockName();
}
// 调用block方法
[self test1];
[self test2];
上面的Demo演示了如何利用Block在方法间传值,但是上面的写法没有任何意义。我们完全可以在test1方法中调用test2方法,block使问题复杂化了。
下面我要演示一个Demo,设置一个tableView,点击tableView的不同行,执行不同的动作。
// viewController.m
#import "ViewController.h"
#import "CellItem.h"
#define screen_width [UIScreen mainScreen].bounds.size.width
#define screen_height [UIScreen mainScreen].bounds.size.height
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, weak) UITableView *tableView;
@property (nonatomic, strong) NSArray *cellItems;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1.创建tableView
[self setupTableView];
// 2.编写tableView的代理方法和数据源方法
// 思路:点击cell,执行特定的动作。可读性较差的写法就是无限嵌套 if else 来完成,但是我们可以封装一个cellItem对象,并且将要执行的动作保存在一个Block之中,这样写会大大增强代码的可读性。
// 额,这个方法名,,,算了,懒得改了
CellItem *item1 = [CellItem cellWithTitle:@"执行动作1"];
item1.block = ^{
NSLog(@"这里执行动作1");
};
CellItem *item2 = [CellItem cellWithTitle:@"执行动作2"];
item2.block = ^{
NSLog(@"这里执行动作2");
};
CellItem *item3 = [CellItem cellWithTitle:@"执行动作3"];
item3.block = ^{
NSLog(@"这里执行动作3");
};
_cellItems = @[item1,item2,item3];
}
#pragma mark - Demo2
- (void)setupTableView
{
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, screen_width, screen_height - 20) style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
_tableView = tableView;
}
#pragma mark ====== UITableViewDataSource & UITableViewDelegate ======
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _cellItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellID = @"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// 可读性差的写法
// if (indexPath.row == 0) {
// cell.textLabel.text = @"执行动作1";
// } else if (indexPath.row == 1) {
// cell.textLabel.text = @"执行动作2";
// } else if (indexPath.row == 2) {
// cell.textLabel.text = @"执行动作3";
// }
CellItem * item = _cellItems[indexPath.row];
cell.textLabel.text = item.title;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 可读性差的写法
// if (indexPath.row == 0) {
// } else if (indexPath.row == 1) {
// } else if (indexPath.row == 2) {
// }
CellItem *item = _cellItems[indexPath.row];
item.block();
}
// CellItem.h
#import <Foundation/Foundation.h>
@interface CellItem : NSObject
@property (nonatomic, strong) NSString *title;
// block的属性定义,按照block的声明去写就好。inlineBlock黑魔法可以帮助你回忆block的书写格式,妈妈再也不用担心我不会写block了!
@property (nonatomic, strong) void(^block)();
// 初始化Item的方法
+ (instancetype)cellWithTitle:(NSString *)title;
@end
// CellItem.m
#import "CellItem.h"
@implementation CellItem
+ (instancetype)cellWithTitle:(NSString *)title
{
CellItem *item = [[CellItem alloc] init];
item.title = title;
return item;
}
@end
Block使用场景:逆向传值
逆向传值的方法有代理传值、Block传值等,其实思路都是大同小异。我们假设有两个VC:A和B,A跳转到B,B返回A时给A传个值。
- 1.在B添加添加一个Block属性
@property (nonatomic, strong) void(^block)(NSString *);
- 2.在A界面执行跳转代码,并调用返回值
-(void)touchesBegan:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event
{
RedViewController *redVc = [[RedViewController alloc] init];
redVc.view.backgroundColor = [UIColor redColor];
redVc.block = ^(NSString *value){
NSLog(@"这是从redVc传递过来的值:%@",value);
};
[self presentViewController:redVc animated:YES completion:nil];
}
- 3.在B界面判断,是否定义Block,如果定义Block,则实现Block并传值。
if (self.block) {
self.block(@"我是值");
}
- 4.执行返回代码,,即可获取到逆向传递的值。
-(void)touchesBegan:(NSSet<UITouch *> *)touches
withEvent:(UIEvent *)event
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Block使用场景:参数使用
方法的参数如果含有 ^ ,则该参数就是个Block参数。
把Block当做参数,并不是马上就调用Block,何时调用由方法的内部决定。
何时需要使用Block当参数:当做的事情由外部决定,而执行时间(顺序)由内部决定。
// Person.h
- (void)wantToEat:(void(^)(NSString *))block;
// Person.m
- (void)wantToEat:(void (^)(NSString *))block
{
// 在这里,我们调整了Block的执行顺序,他可以在log之前执行,也可以在log之后执行,取决于内部实现。
NSLog(@"我饿了!");
if (block) {
block(@"橘子");
}
}
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
[p wantToEat:^(NSString *food) {
NSLog(@"我想吃%@!",food);
}];
}
Block使用场景:方法返回值
我们在使用第三方框架Masonry时,看到它的用法:
make.center.equalTo(ws.view);
这种 . 语法的形式,其实就是通过使用Block方法返回值来实现的
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.test(@"1").test(@"2");
}
- (ViewController *(^)(NSString *))test
{
// 第一个return,是return该方法的返回值,一个Block
return ^ViewController *(NSString *value){
NSLog(@"%@",value);
// 第二个return是return Block的返回值。
return self;
};
}
未完待续……