block介绍
block 也叫做语法块或者闭包或者匿名函数。通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。而在很多框架中,block越来越经常被用作回调函数,取代传统的回调方式。自己平时在开发中也比较喜欢用 block 进行开发, 今天就来说一说 block.
优点
用block作为回调函数,可以使得程序员在写代码更顺畅,不用中途跑到另一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比较合适。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。
另一个好处,就是采用block作为回调,可以直接访问局部变量。比如我要在一批用户中修改一个用户的name,修改完成后通过回调更新对应用户的单元格UI。这时候我需要知道对应用户单元格的index,如果采用传统回调方式,要嘛需要将index带过去,回调时再回传过来;要嘛通过外部作用域记录当前操作单元格的index(这限制了一次只能修改一个用户的name);要嘛遍历找到对应用户。而使用block,则可以直接访问单元格的index。
block 用法详解
定义block
//block语法块 其实就是一个匿名函数,但是他和其他函数不一样,他可以嵌套定义,如果想使用 block 语法块就需要用对应数据类型的 block 变量取接收,使用变量进行调用
void (^sayBlock)() = ^ void (){
NSLog(@"没有名字的函数");
};
//使用 block变量
sayBlock();
PS: 1.数据类型为 void (^)()
2.变量名位 sayBlock
3.初值为^ void(){NSLog(@“没有名字的函数”);}
block 简单用法
- (void)viewDidLoad
{
[super viewDidLoad];
//(1)定义无参无返回值的Block
void (^printBlock)() = ^(){
printf("no number");
};
printBlock();
printBlock(9);
int mutiplier = 7;
//(3)定义名为myBlock的代码块,返回值类型为int
int (^myBlock)(int) = ^(int num){
return num*mutiplier;
}
//使用定义的myBlock
int newMutiplier = myBlock(3);
printf("newMutiplier is %d",myBlock(3));
}
//定义在-viewDidLoad方法外部
//(2)定义一个有参数,没有返回值的Block
void (^printNumBlock)(int) = ^(int num){
printf("int number is %d",num);
};
定义Block变量,就相当于定义了一个函数。但是区别也很明显,因为函数肯定是在-viewDidLoad方法外面定义,而Block变量定义在了viewDidLoad方法内部。当然,我们也可以把Block定义在-viewDidLoad方法外部,例如上面的代码块printNumBlock的定义,就在-viewDidLoad外面。
再来看看上面代码运行的顺序问题,以第(3)个myBlock距离来说,在定义的地方,并不会执行Block{}内部的代码,而在myBlock(3)调用之后才会执行其中的代码,这跟函数的理解其实差不多,就是只要在调用Block(函数)的时候才会执行Block体内(函数体内)的代码。
根据上面的简单代码得到总结:
(1)在类中,定义一个Block变量,就像定义一个函数;
(2)Block可以定义在方法内部,也可以定义在方法外部;
(3)只有调用Block时候,才会执行其{}体内的代码;
(PS:关于第(2)条,定义在方法外部的Block,其实就是文件级别的全局变量)
block 页面传值
场景如此: 第一个页面中有一个 UILabel , 第二个页面中有个一 UITextField, 在第二个页面中输入文字, 传入至第一个页面中, 更新至第一个页面的 Label 上.
即页面传值的从后向前传值, 我认为页面传值 block 还是性能较好, 而且代码不繁杂, 代码清晰. 或者使用通知中心传值也很方便, 但使用代理来做的话就代码就繁杂一些了.
ViewController :
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *showLabel;
- (IBAction)btnPress:(id)sender;
@end
#import "ViewController.h"
#import "SecondViewController.h"
@interface ViewController ()
@property(strong,nonatomic) SecondViewController *vc;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_showLabel.text = @"第一次";
self.vc = [self.storyboard instantiateViewControllerWithIdentifier:@"second"];
__weak ViewController *weakSelf = self;
self.vc.returnTextBlock = ^(NSString *str) {
weakSelf.showLabel.text = str;
};
}
- (IBAction)btnPress:(id)sender {
[self.navigationController pushViewController:self.vc animated:YES];
}
@end
SecondViewController :
typedef void (^ReturnTextBlock)(NSString *str);
@interface SecondViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *inputText;
@property (nonatomic,copy) ReturnTextBlock returnTextBlock;
@end
#import "SecondViewController.h"
@interface SecondViewController ()
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (self.returnTextBlock != nil) {
self.returnTextBlock(self.inputText.text);
}
}
@end
block 封装回调应用
下面封装了一个类继承于 UIView, 上面有一个按钮, 当点击按钮的时候, 会执行在controller 中的回调, 这时候也会拿到从 TitleView 类里面传出来的数据, 在这里传入的是 UIButton, 你可以根据自己需要进行封装.
TitleView :
@interface TitleView : UIView
-(void)settingTitleViewWithShowMarketListHandle:(void(^)())showMarketListHandle;
@end
#import "TitleView.h"
@interface TitleView ()
@property (nonatomic, copy) void(^showMarketList)(UIButton *button);
@end
@implementation TitleView
-(void)settingTitleViewWithShowMarketListHandle:(void(^)(UIButton *button))showMarketListHandle
{
self.showMarketList = showMarketListHandle;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 200, 100);
[button addTarget:self action:@selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
button.tag = 888;
[self addSubview:button];
}
#pragma mark - 私有方法
- (void)clickButton:(UIButton *)button
{
NSLog(@"点击了");
_showMarketList(button.tag);
}
@end
ViewController :
TitleView *titleView = [[TitleView alloc] initWithFrame:CGRectMake(0, 0, 300, 100)];
[self.view addSubviews:titleView];
[titleView settingTitleViewWithShowMarketListHandle:^(UIButton *button) {
NSLog(@"button's tag = %ld",button.tag);
}];
block 注意事项
1.在使用block前需要对block指针做判空处理。
比如在之前例子中的代码:
if (self.returnTextBlock != nil) {
self.returnTextBlock(self.inputText.text);
}
2.在Block的{}体内,是不可以对block外的变量进行更改的,所以需要在 block 外用__block关键字对变量进行修饰, 这样在Block内,就可以修改外部变量了。
//将Block定义在方法内部
int x = 100;
void (^sumXAndYBlock)(int) = ^(int y){
x = x+y;
printf("new x value is %d",x);
};
sumXAndYBlock(50);
3.循环引用的问题, 在使用 block 的时候, 尤其是在 MRC 中, 使用不当会造成 ViewController或者封装类不能正常 dealloc. 比如在controller 的 block中直接使用 self 时, MRC 中没有 release 时,等等.
4.在MRC的编译环境下,在使用block之后要对,block指针做赋空值处理,要先release掉block对象。block作为类对象的成员变量,使用block的人有可能用类对象参与block中的运算而产生上面第3点循环引用的问题。将block赋值为空,是解掉循环引用的重要方法。(不能只在dealloc里面做赋空值操作,这样已经产生的循环引用不会被破坏掉)
if (data != nil && _sucBlock != NULL) {
_sucBlock(data);
}
//MRC下:要先将[_sucBlock release];(之前copy过)
_sucBlock = nil; //Importent: 在使用之后将Block赋空值,解引用 !!!
5.block 添加属性时, 声明的关键字应该用 copy, 因为 block在外传时, 需要通过赋值上栈才能传出去, 就可以想到 strong / copy , 但如果用 strong 的时候, 可能会引起循环引用, 故 copy 是最佳选择.
6.在controller 中回调 block 时, 不可以直接使用 self, 否则controller 的 dealloc 不会正常执行, 因为在 block 中可能会持有当前 controller 中的一些成员变量, 所以双方都不会释放. 所以就需要在 block 外进行__weak 将 self 转化.
但是在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。weakSelf比如在获取属性时, weakSelf.name 引用计数不会+1, 但 strongSlef.name 引用计数会+1 , 所以不会被析构. 注意, Block的引用循环和弱引用问题与单线程、多线程无关。只与内存模型和生存周期有关.
第四、第五条合起来有个名词叫weak–strong dance,以下代码来自AFNetworking,堪称使用weak–strong dance的经典。
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
7.weakSelf 的析构出现的几率很低, 是不容易出现的. 除了autorelease之外的延迟效应,通常的情况是,外层有引用 指向 这个self对象。self 指向block, block里面有一个弱引用会指向self。由于外层的引用不释放,那么里面的这些引用都有效. 那个weakSelf 变为nil的机会,只有以下条件同时满足:
(1)外层没有其他引用 执行 Self对象
(2)Self对象没有接受过autorelease消息
(3)发现Self现在的retain count为0,ARC触发 回收Self对象,然后将weakSelf置为nil
8.根据官方文档Transitioning to ARC Release Notes中所说的, 平时开发中不重要的 block 可以一直用 __weak的指针, 但是在比较重要的 block 中,需要写成如下(官方文档例子):
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
不仅使用过了weak–strong dance, 而且还有一个if (strongMyController) 的判断, 所以比6中的写法更加严谨.
最后再推荐一篇关于block 更深层探究的好文章: 对Objective-C中Block的追探