在iOS 4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调。这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用:
bool executeSomeTask(void) {
//do something and return if success or not
}
bool (*taskPoint)(void);
taskPoint = something;
上面的函数指针可以直接通过(*taskPoint)()的方式调用executeSomeTask这个函数,这样对比block跟似乎C语言的函数指针是一样的,但是两者仍然存在以下区别:
block的代码是内联的,效率高于函数调用
block对于外部变量默认是只读属性
block被Objective-C看成是对象处理
小结:
1. block 是 C 语言的
2. block 是一种匿名函数
3. 是一种数据类型,可以当作参数传递
4. 是一组预先准备好的代码,在需要的时候执行
Block 应用场景:
1. 自定义视图的反向传值
2. Modal / POP 控制器的反向传值
3. 异步方法执行完毕后的反向传值
注:
1. Block 的反向传值一般被称为回调
2. 反向传递的树枝通过 Block 的参数传递
Block 的定义和匿名函数
定义 block 的函数体
1. 以^起始,表示是一个block
2. 以{}包装block中的代码块
^ {
NSLog(@"hello block");
};
提示:Expression result unused=> 表达式没有使用
使用一个变量记录 block
// block 是一个没有参数,没有返回值的匿名函数
// myBlock 是记录这个匿名函数的变量名
void(^myBlock)() = ^ {
NSLog(@"hello block");
};
提示:Unused variable 'myBlock'=>'myBlock' 没有使用
使用变量
// block 是一个没有参数,没有返回值的匿名函数
// myBlock 是记录这个匿名函数的变量名
void(^myBlock)() = ^ {
NSLog(@"hello block");
};
// 执行 myBlock 变量中记录的代码myBlock();
Block 的定义可以借助inlineBlock速记,但是:
1. 不要过份依赖inlineBlock
2. block的手写定义一定要过关!
当做参数传递 Block
定义一个接收并且执行 block 的方法
// 接收并且执行 block
- (void)callBlock:(void(^)())completion {
NSLog(@"干点什么");
completion();
}
定义 block 并且当做参数传递
#pragma mark - Block 当做参数传递
- (void)blockDemo2 {
void(^myBlock)() = ^ {
NSLog(@"hello block");
};
[selfcallBlock:myBlock];
}
通过 Block 的参数回调
准备一个方法,模拟回调网络请求结果
// 通过 block 的参数回调模拟网络请求的结果
- (void)callBlock2:(void(^)(NSString*result))completion {
NSLog(@"网络加载了一点数据");
NSString*jsonString =@"我是网络加载的 json 字符串";
completion(jsonString);
}
定义 block 并且传递参数
#pragma mark - Block 通过参数回调
- (void)blockDemo3 {
// 1. 定义 blockvoid(^myBlock)(NSString*) = ^ (NSString*json) {
NSLog(@"%@", json);
};
// 2. 调用方法
[selfcallBlock2:myBlock];
}
block的反向传值
来一个小demo:
点击PUSH按钮在最右侧控制器输入用户名
点击保存按钮 POP 控制器,并且将用户名显示在nameLabel中
项目准备
右侧的 DemoViewController
在DemoViewController中定连线属性
@interface DemoViewController()
@property(weak,nonatomic)IBOutlet UITextField *nameText;
@end
连线方法
/// 保存并返回
- (IBAction)save:(id)sender {
// 将输入内容传回
// 导航控制器弹栈
[self.navigationController popViewControllerAnimated:YES];
}
在 .h 中定义属性
/// 输入完成 block 回调
@property(nonatomic,copy)void(^inputCompletion)(NSString*userName);
修改 save 方法
// 保存并返回
- (IBAction)save:(id)sender {
// 判断 block 属性是否有内容
if(self.inputCompletion != nil) {
// 执行完成回调将输入内容传回
self.inputCompletion(_nameText.text);
}
// 导航控制器弹栈
[self.navigationController popViewControllerAnimated:YES];
}
左侧的 viewController
导入头文件
#import"DemoViewController.h"
属性连线
@property(weak,nonatomic)IBOutlet UILabel*nameLabel;
实现prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
// 1. 获取目标控制器
DemoViewController *vc = segue.destinationViewController;
// 2. 设置完成回调
[vc setInputCompletion:^(NSString*name) {
self.nameLabel.text= name;
}];
}
与代理的对比
实现步骤的对比
被调用方(子控制器)
代理
定义协议
定义代理属性
需要的时候通知代理执行协议方法
block
定义 block 属性
需要的时候执行 block 属性
调用方
代理
设置代理
遵守协议
实现协议方法
block
设置 block 属性,准备需要执行的代码
代码实现的对比
Block 的优势
使用 block 所有的代码写在一起,更加便于维护和阅读
使用 代理 代码想对松散
Block 的弱势
一个回调对应一个属性,属性定义在类的头文件中,容易和其他属性混在一起
代理设计模式通过协议预先定义好代理的行为,从设计模式而言,更加严谨
尤其在协议方法很多的时候,使用代理更加容易上手!
使用 block 需要注意循环引用!
block 保存的内存区域
不引用任何外部变量的 block
- (void)blockDemo1 {
void(^myBlock)() = ^ {
NSLog(@"hello world");
};
NSLog(@"%@", myBlock);
}
不引用任何外部变量的 block 保存在全局区__NSGlobalBlock__
从而可以保证无论 block 如何被传递,内部包装的代码都不会发生变化,而且执行效率高
但是实际开发中,不引用外部变量的 block 几乎是不存在的
引用外部变量的 block
- (void)blockDemo2 {
int i =10;
void(^myBlock)() = ^ {
NSLog(@"hello world %zd", i);
};
NSLog(@"%@", myBlock);
}
引用外部变量的 block 保存在:
ARC:堆区__NSMallocBlock__
MRC:栈区__NSStackBlock__
因此:在定义block 属性时应该使用copy关键字,将 block 从栈区复制到堆区
定义 block 属性
/// 定义 block 属性
@property(nonatomic,copy)void(^demoBlock)();
记录 block 属性
- (void)blockDemo2 {
int i =10;
void(^myBlock)() = ^ {
NSLog(@"hello world %zd", i);
};
NSLog(@"%@", myBlock);
// 错误的写法,不会调用 setter 方法
// _demoBlock = myBlock;
// 正确的写法,调用 setter 方法,并且对 block 进行 copy
self.demoBlock= myBlock;
NSLog(@"%@",self.demoBlock);
}
提示:为了避免程序员的麻烦,在 ARC 中,定义了引用外部变量的 block,默认都是在堆区的!
block的循环引用
定义一个引用self.的 block
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
void(^block)() = ^ {
NSLog(@"%@",self.view);
};
block();
}
- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}
@end
运行测试
没有循环引用:因为 block 执行完毕后会释放,从而释放对self的引用
定义属性
@property(nonatomic,copy)void(^demoBlock)();
在viewDidLoad中记录 block
- (void)viewDidLoad {
[superviewDidLoad];
void(^block)() = ^ {
NSLog(@"%@",self.view);
};
// 记录 block,等待需要的时候执行
self.demoBlock= block;
}
运行测试
循环引用发生了:
因为ViewController引用了block属性
block内部引用了self(ViewController)
相互引用产生了循环引用
解决循环引用
使用__weak修饰符,定义一个弱引用的对象
然后让block引用此弱引用对象,从而可以打破循环引用
__weak typeof(self) weakSelf =self;
void(^block)() = ^ {
NSLog(@"%@", weakSelf.view);
};
提示:
在使用block的时候,如果出现self,同时使用属性记录 block 的时候,要格外注意是否会出现循环引用
注意:不是所有的self.都会出现循环引用 ——block 执行完毕就销毁,例如 UIView 的动画代码
大坑:在block内部不要使用_成员变量!
因为,成员变量默认是由控制器强引用的,以下代码同样会出现循环引用,同时非常不容易发现!
@implementation ViewController{
NSMutableArray*_arrayM;
}
- (void)viewDidLoad {
[superviewDidLoad];
__weak typeof(self) weakSelf =self;
void(^block)() = ^ {
NSLog(@"%@ %@", weakSelf.view, _arrayM);
};
// 记录 block,等待需要的时候执行
self.demoBlock= block;
}