前言
代码块,一块代码,一个简单的匿名函数体。
目标
理解代码块
理解如何让设备同时执行多个任务,也就是并发性。
内容
1.代码块
代码块对象(简称“代码块”)是对C语言中函数的扩展。
除了函数中的代码,代码块还包含变量绑定。代码块有时也被称为闭包(closure).
代码块包含两种类型的绑定:自动型与托管型。
自动绑定(automatic binding)使用的是栈中的内存。
托管绑定(manageed binding)使用的是堆中的内存。
因为代码块实际上是由C语言实现的,所以它们在各种以C作为基础的语言内都是有效的,包括Objective-C、C++以及Objective-C++。
在研究代码块之前,先来讨论一下C语言中的函数指针。为什么呢?因为代码块借鉴了函数指针的语法。
与函数指针类似,代码块具有以下特征:
1)返回类型可以手动声明也可以由编译器推导;
2)具有指定类型的参数列表;
3)拥有名称。
声明函数指针:
void (*my_func)(void);
声明代码块:
void(^my_block)(void);
例:
int(^square_block)(int number) = ^(int number){
return (number *number);
};
int result = square_block(5);
printf("Result = %d\n",result);
等号前面是代码块的定义,而等号后面是实现内容。
一般我们可以用如下关系来表示它们:
<returntype>(^blockname)(list of arguments) = ^(arguments){body;};
编译器可以通过代码块的内容推导出返回类型,所以可以省略,如果代码块没有参数,也可以省略。
例:
void (^theBlock)() = ^{printf("Hello Block!\n");};
使用时可以像函数一样使用。
int result = square_block(5);
2.直接使用代码块
使用代码块时通常不需要创建一个代码块变量,而是在代码内联代码块的内容。
通常,在需要代码块做参数的方法或函数里直接使用。
NSArray *array = [NSArray arrayWithObjects:@"Amir",@"Mishal",@"Irrum",nil];
NSLog(@"Unsorted Array %@",array);
NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1,NSString *object2){
return [object1 compare:object2];
}];
NSLog(@"Sorted Array %@",sortedArray);
本地变量
本地变量就是与代码块在同一范围内声明的变量。
typedef double(^MKSampleMultiplyBlockRef)(void);
double a = 10,b = 20;
MKSampleMultiplyBlockRef multply = ^(void){return a*b;};
NSLog(@"%f",multiply());
a = 20;
b = 50;
NSLog(@"%f",multiply());
第二个NSLog会输出什么呢?
结果是200.因为变量就是本地的,代码块会在定义时复制并保存它们的状态。
全局变量
static double a = 10,b = 20;
MKSampleMultiplyBlockRef multiply = ^(void){return a * b;};
NSLog(@"%f",multiply());
a = 20;
b = 50;
NSLog(@"%f",multiply());
在异步编程时我们经常进行函数回调,由于函数调用是异步执行的,我们如果想让一个操作执行完之后执行另一个函数,则无法按照正常代码书写顺序进行编程,因为我们无法获知前一个方法什么时候执行结束,此时我们经常会用到代码块。
代码块(Block)就是一个函数体(匿名函数),它是Objective-C对于闭包的实现,在代码块中我们可以持有或引用局部变量,同时利用Block你可以将一个操作作为一个参数进行传递(是不是想起了C语言中的函数指针)。
在下面的例子中我们将使用Block实现上面的点击监听操作:
KCButton.h
//
// KCButton.h
// Protocol&Block&Category
//
#import <Foundation/Foundation.h>
@class KCButton;
typedef void(^KCButtonClick)(KCButton *);
@interface KCButton : NSObject
#pragma mark - 属性
#pragma mark 点击操作属性
@property (nonatomic,copy) KCButtonClick onClick;
//上面的属性定义等价于下面的代码
//@property (nonatomic,copy) void(^ onClick)(KCButton *);
#pragma mark - 公共方法
#pragma mark 点击方法
-(void)click;
@end
KCButton.m
//
// KCButton.m
// Protocol&Block&Category
//
#import "KCButton.h"
@implementation KCButton
-(void)click{
NSLog(@"Invoke KCButton's click method.");
if (_onClick) {
_onClick(self);
}
}
@end
main.m
//
// main.m
// Protocol&Block&Category
//
#import <Foundation/Foundation.h>
#import "KCButton.h"
int main(int argc, const char * argv[]) {
KCButton *button=[[KCButton alloc]init];
button.onClick=^(KCButton *btn){
NSLog(@"Invoke onClick method.The button is:%@.",btn);
};
[button click];
/*结果:
Invoke KCButton's click method.
Invoke onClick method.The button is:<KCButton: 0x1006011f0>.
*/
return 0;
}
上面代码中使用Block同样实现了按钮的点击事件,关于Block总结如下:
1. Block类型定义:返回值类型(^ 变量名)(参数列表)(注意Block也是一种类型);
2. Block的typedef定义:返回值类型(^类型名称)(参数列表);
3. Block的实现:^(参数列表){操作主体};
4. Block中可以读取块外面定义的变量但是不能修改,如果要修改那么这个变量必须声明_block修饰;
并发性
现在的iOS设备都是多核的,这意味着你可以在同一时间进行多项任务。能够同一时间进行多项任务的程序称为并发的(concurrent)程序。
为了减轻在多核上编程的负担,苹果公司引入了Grand Central Dispatch,我们称之为GCD。
Objective -C提供了一个语言级别的(language-level)关键字@synchronized。这个关键字拥有一个参数,通常这个对象是可以修改的。
@sychronized(theObject)
{
}
它可以确保不同的线程会连续地访问临界区的代码。
若你定义了一个属性并且没有指定关键字nonatomic作为属性的特性,编译器会生成强制彼此互斥的getter和setter方法。
编译器生成了synchronize(mutex,atomic)语句来确保彼此互斥。这样设置代码和变量会产生一些消耗,它会比直接访问更慢一些。
调度队列
GCD可以使用调度队列(dispatch queue),它与线程很相似但使用起来更简单。
只需写下你的代码,把它指派为一个队列,系统就会执行它。你可以同步或异步执行任意代码。
一共有以下3种类型的队列:
1.连续队列:每个连续队列都会根据指派的顺序执行任务。
2.并发队列:每个并发队列都能并发执行一个或多个任务。
3.主队列:它是应用程序中有效的主队列,执行的是应用程序的主线程任务。
连续队列
有时有一连串任务需要按照一定的顺序执行,这时便可以使用连续队列。任务执行顺序为先入先出(FIFO):只要任务是异步提交的,队列会确保任务根据预定的顺序执行。这些队列都是不会发生死锁的。
死锁(deadlock)是一个令人不悦的情况,指的是两个或多个任务在等待他方运行结束,就像是几辆汽车同时位于一个很拥挤的停车场里。
使用连续队列
dispatch_queue_t my_serial_queue;
my_serial_queue = dispatch_queue_create("com.apress.MySerialQueue1",NULL);
参数是队列名称,第二个负责提供队列的特性(现在用不到,所以必须是NULL)。
当队列创建好之后,就可以给它指派任务。
并发队列
并发调度适用于那些可以并行运行的任务。并发队列也遵从先入先出(FIFO)的规范,且任务可以在前一个任务结束前就开始执行。一次所运行的任务数量是无法预测的。
每个应用程序都有3种并发队列可以使用:
- 高优先级(high):DISPATCH_QUEUE_PRIORITY_HIGH
- 默认优先级(default): DISPATCH_QUEUE_PRIORITY_DEFAULT
- 低优先级(low): DISPATCH_QUEUE_PRIORITY_LOW
例:
dispatch_queue_t myQueue;
myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
主队列
使用dispatch_get_main_queue可以访问与应用程序主线程相关的连续队列。
dispatch_queue_t main_queue = dispatch_get_main_queue(void);
因为这个队列与主线程相关,所以必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。通常要以同步方式使用这个队列,提交多个任务并在它们 操作完毕后执行一些动作。
获取当前队列
你可以通过调用dispatch_get_current_queue()来找出当前运行的队列代码块。
如果你在代码块对象之外调用了这个函数,则它将会返回主队列。
dispatch_queue_t myQueue = dispatch_get_current_queue();
添加任务
有两种方式可以向队列中添加任务。
同步:队列会一直等待前面任务结束。
异步:添加任务后,不必等待任务,函数会立刻返回。推荐优先使用这种方式,因为它不会阻塞其他代码的运行。
总结+废话
介绍了代码块,以及代码块的使用。
并发性有点复杂。在介绍UI之后还会详细讲解。