Working with Blocks
简介
Blocks是C语言层级语法和运行时特性。 它们类似于标准C函数,但是除了可执行代码之外,它们还可以保存堆栈变量。 因此,块可以保存数据,在代码执行时使用。
1、Block可以作为函数数调用、作为函数参数、作为方法参数。
2、因为独立完整可以在多线程中使用;
3、因为拥有回调时需要执行的代码和执行代码时需要的数据,常常被用来实现回调Callback。
由于Objective-C和C++都是从C派生,因此三种语言均可以使用(Objective-C使用更多)。iOS从4.0版本开始支持。在其他语言环境中有时被称为“闭包”。
特点
Block是一个匿名的内联代码集合,有以下特点:
1、像函数一样用参数列表
2、有可推断或者声明的返回值类型
3、可以从定义它作用域捕获数据
4、可选的可以修改捕获到的数据
5、同一作用域中的其它block共享捕获的数据
6、作用域的堆栈被销毁后,仍然可以继续共享定义其范围定义的数据
由于compiler 和 runtime 保证block和其相关的数据的生命周期,因此可以copy一份传递到其他地方使用。
声明和创建
声明
首先说明下函数指针的声明格式
返回值类型 ( * 指针变量名) ([形参列表]);
Block variables保存着block的引用。可以像声明函数指针一样声明block变量,但是要把*
换成^
,例如以下均为有效的声明
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
Blocks支持可变的参数列表,当没有参数时必须写void。
通过为编译器提供block使用的数据,传递参数,返回值,block设计完全类型安全。可以把block引用强制转换成任意类型的指针,反之亦然。但是不能使用操作符*
来访问值,因为block的值在编译期无法计算。
理解:假如每次方法调用当做一次消息发送(一般底层会有很多次),把block中的所以消息发送按照顺序放到一种能够先进先出的数据结构--比方说实现栈特性的结构体;那么block变量
和^{ ... }
作用一样都是指向结构体的首地址;传递首地址跟其他对象指针、函数指针用法很像。
类型定义简化用法
如果经常重复使用同一个类型的block,你可以定义自己的block类型,例如
//返回值类型:float 两个参数类型:float 变量名称:MyBlockType
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
创建
^
表示block的开始,接一个返回值类型(可选,默认不写)
再接一个(参数列表)
,后边接一个{代码块};
例如
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
解读如下图所示
如果没有明确声明返回值类型,则根据block代码内容推断具体类型,例如
int multiplier = 7;
//未指明返回值类型
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
//指明返回值类型
int (^myBlock)(int) = ^ int (int num) {
return multiplier;
};
Blocks和变量
首先可以像函数一样引用三种标准类型的变量:
1、全局变量,包括静态局部变量
2、全局函数
3、作用域内的局部变量和参数
其次Blocks还支持另外两种类型:
4、__block修饰的变量
5、const导入的
所以Blocks内部处理变量的五种情况:
1、全局变量(包括作用域内的静态局部变量)可以直接访问
2、Blocks的参数可以直接访问
3、作用域内非静态局部变量,作为const变量引用(只读),强行修改编译器报错
4、被__block修饰的作用域内非静态局部变量,可以直接访问
5、Blocks内部声明的局部变量,可以直接访问
示例代码如下
int global_var = 1;
- (void)viewDidLoad {
[super viewDidLoad];
static int static_var = 2;
__block int loacal_var = 3;
__block int const_var = 3;
void (^myBlock)(int) = ^(int number) {
global_var = global_var * 10;
static_var = static_var * 10;
loacal_var = loacal_var * 10;
number = number * 10;
NSLog(@"局部变量:%d ,说明局部变量可以读取",const_var);
NSLog(@"参数变量原:4 修改后%d,说明可以正常访问",number);
};
myBlock(4);
NSLog(@"全局变量原:1 修改后:%d,说明可以正常访问 ",global_var);
NSLog(@"静态变量原:2 修改后:%d,说明可以正常访问 ",static_var);
NSLog(@"__block修饰的局部变量原:3 修改后:%d,说明可以正常访问 ",loacal_var);
}
//输出结果
局部变量:3 ,说明局部变量可以读取
参数变量原:4 修改后40,说明可以正常访问
全局变量原:1 修改后:10,说明可以正常访问
静态变量原:2 修改后:20,说明可以正常访问
__block修饰的局部变量原:3 修改后:30,说明可以正常访问
Blocks和对象
对象通过Properties来引用Blocks。语法和定义Blocks语法类似;例如
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
self.blockProperty = ^{
...
};
self.blockProperty();
注意:此处应该用copy,因为block需要被copy来保存引用的外界的变量,无需关心引用计数问题,编译器自动进行管理。
还可以通过自定义类型简化
typedef void (^XYZSimpleBlock)(void);
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end
避免循环引用
当变量变成实例变量的时候,需要避免循环引用的问题。由于Blocks会持有使用的变量,比如在Viewcontroller中,block作为一个property被self持有,block中使用self调用方法,这样便造成循环引用的问题使得二者在不使用时均得不到释放。解决方法是在block中使用self的弱引用,用__weak修饰。
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
用法
作为变量
如果声明Block作为一个变量,可以像函数一样调用。例如
int (^oneFrom)(int) = ^(int anInt) {
return anInt - 1;
};
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
float (^distanceTraveled)(float, float, float) =
^(float startingSpeed, float acceleration, float time) {
float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
return distance;
};
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9
作为函数参数
可以像传递其它参数一样传递block。大多数的情况不需要声明,只需要作为参数以内联的方式实现。例如
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
//根据首字符进行一次排序
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
//此函数表示:以参数3的长度计算前2个参数的差值
return strncmp(left, right, 1);
});
// Block implementation ends at "}"
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
作为方法参数
Cocoa Touch 提供了很多方法使用Blocks。可以传递block作为参数使用。例如
//是否包含指定字符串
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
*stop = YES;
found = YES;
}
}];
// At this point, found == YES
作用
很大程度上起到了简化的作用。
简化回调
Blocks可以用处替换传统回调的原因如下:
1、方法调用和回调代码集中在一起;还常作为framework methods的参数。
2、可以直接访问局部变量。
简化枚举
对一个数组进行枚举,常用的方法有3种
1、for循环
2、for的泛型遍历for (type *object in collection)
3、使用带有block的简化方法。例如- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
//for循环
for (int i = 0; i < array.count; i ++)
{
NSString *item = array[i];
NSLog(@"index:%d value:%@",i,item);
}
//泛型遍历
for (NSString *item in array)
{
NSLog(@"value:%@",item);
}
//枚举方法
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"index:%ld value:%@",idx,obj);
}];
简化并发任务
每个block都是独立的工作单元,包含可执行代码和保存使用的数据,这使得它能够在多线程开发中被异步调用。
系统提供了一系列的多线程编程技术,其中包括两种任务调度机制:Operation queues 和 Grand Central Dispatch(GCD)。不像Thread关注怎么样管理运行,把需要执行的任务打包到blocks中,然添加到队列中,然后交给系统根据当时的资源自动执行。
简化Operation Queues
//定义任务
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];
// 主线程执行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
// 后台执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
更多知识请阅读Operation Queues
简化Grand Central Dispatch
//获取并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//定义任务添加到队列并执行
dispatch_async(queue, ^{
NSLog(@"Block for asynchronous execution");
});
更多知识请阅读 Dispatch Queues