iOS 开发之 Block

Posted by Calvin on 2017-07-06

关于 Block

在 iOS4.0之 后,block 横空出世,它本身封装了一段代码并将这段代码当做变量,通过 block() 的方式进行回调。这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用:

bool executeSomeTask(void) {
//do something and return if success or not
}
bool (*taskPoint)(void);
taskPoint = something;

上面的函数指针可以直接通过 (*taskPoint)() 的方式调用 executeSomeTask 这个函数,这样对比 block 跟似乎 C 语言的函数指针是一样的,但是两者仍然存在以下区别:

  1. block 的代码是内联的,效率高于函数调用
  2. block 对于外部变量默认是只读属性
  3. block 被 Objective-C 看成是对象处理

Block 特性

认识 Block

Block 代码结构如下:

20170706149931057994690.jpg

先来看一个简单的block吧:

BOOL (^isInputEven)(int) = ^(int input) {
if (input % 2 == 0) {
return YES;
} else {
return NO;
}
};

以上定义了一个 block 变量,block 本身就是一个程序段,因此有返回值有输入参数,这里这个 block 返回的类型为 BOOL^符号表示 block 定义的开始,block 的名称紧跟在^符号之后,这里block的名称是 isInputEven。这段 block 接受一个 int 型的参数,而在等号后面的 int input 是对这个传入 int 参数的说明:在该 block 内,将使用 inpu t这个名字来指代传入的 int 参数。

调用这个block的方法就非常简单和直观了,类似调用c函数的方式即可:

int x = -101;
NSLog(@"%d %@ number", x, isInputEven(x) ? @"is an even" : @"is not an even");

不出意外的话输出为 -101 is not an even number

Block 递归调用

Block 想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候 Block 变量就初始化了,可以递归调用。

static void (^ const blocks)(int) = ^(int i)
{
if (i > 0) {
NSLog(@"num:%d", i);
blocks(i - 1);
}
};
blocks(3);

打印结果如下:

num:3
num:2
num:1

在 Block 中使用局部变量和全局变量

使用全局变量

int global = 1000;
int main(int argc, const char * argv[])
{
@autoreleasepool {
void(^block)(void) = ^(void)
{
global++;
NSLog(@"global:%d", global);
};
block();
NSLog(@"global:%d", global);
}
return 0;
}

打印结果如下:

global:1001
global:1001

而局部变量可以使用,但是不能改变。

int local = 500;
void(^block)(void) = ^(void)
{
local++;
NSLog(@"local:%d", local);
};
block();
NSLog(@"local:%d", local);

在代码块中改变局部变量编译不通过。怎么在代码块中改变局部变量呢?在局部变量前面加上关键字:__block

__block int local = 500;
void(^block)(void) = ^(void)
{
local++;
NSLog(@"local:%d", local);
};
block();
NSLog(@"local:%d", local);

运行结果:

local:501
local:501

Block 回调

先定义 RootViewController,初始化一个 UILabel 和 UIButton,点击 Button 页面跳转到 SecViewController,在 SecViewController 文本框内输入文字并返回到 RootViewController,此时在 SecViewController 页面输入的文本展示到 RootViewController 上。

需要回调数据的是 Root 视图,那么 Block 就应该在 SecView 中定义,用于获取传入回调数据。

我们在 SecViewController.h 定义了 typedef void (^BlockValue)(NSString *value) 的别名为 BlockValue

#import <UIKit/UIKit.h>
typedef void (^BlockValue)(NSString *value);
@interface SecViewController : UIViewController
@property (copy, nonatomic) BlockValue myBlock;
@end

点击 Sec 页面的按钮时添加 BlockValue 的传参操作:

-(void)btnClicked{
self.myBlock(self.textField.text);
[self dismissViewControllerAnimated:YES completion:nil];
}

这样我们就可以在想要获取数据回调的地方,也就 RootViewController 的视图中调用 block:

-(void)btnClicked{
SecViewController *sec = [[SecViewController alloc]init];
// 弱引用转换,为了防止循环引用
__weak RootViewController *weakSelf = self;
sec.myBlock = ^(NSString *value) {
weakSelf.label.text = value;
};
[self presentViewController:sec animated:YES completion:nil];
}

效果如下:

2017070614993130281040.gif