代码块和并发性
Posted 这波lucio来全学了
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代码块和并发性相关的知识,希望对你有一定的参考价值。
第14章 代码块和并发性
14.1 代码块
代码块对象(通常称为代码块)是对C语言中函数的扩展。除了函数中的代码,代码块还包含变量绑定。代码块有时也称为闭包(closure)。
代码块包含两种类型的绑定:自动型和托管型。自动绑定(automatic binding)使用的是栈中的内存,而托管绑定(managed binding)是通过堆创建的。
14.1.1 代码块和函数指针
代码块借鉴了函数指针的语法。与函数指针相似,代码块具有以下特征:
- 返回类型可以手动声明,也可以由编译器推导;
- 具有指定类型的参数列表
- 拥有名称
1. 代码块的定义和实现
int (^square_block)( int number ) = ^(int number)
return (number * number);
;
int result = square_block(6);
NSLog(“Result = %d “,result);
说明:
等号前面的内容:int (^square_block)( int number ),是代码块的定义。
等号后面的内容:是代码块的实现内容。
一般我们可以用如下关系来表示它们:
<returntype> ( ^ blockname) ( list of arguments ) = ^( arguments ) body; ;
返回类型可以省略。
2. 使用代码块
可以像函数一样使用代码块。例如:
int result = square_block(6);
使用代码块的时候通常不需要创建一个代码块变量,而是在代码中内联代码块的内容。通常需要将代码块作为参数的方法或函数。
NSArray *array = [NSArray arrayWithObjects:@“a”,@“b”,@“c”,@“d”,nil];
NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1, NSString *object2)
return [object1 compare: object2];
];
3. 使用typedef关键字
像上面那样那么长的变量定义语句,在输入这些代码的时候很容易引起错误。我们可以用typedef关键字。
typedef double (^ MyBlockName)(double a, double b);
这行代码定义了一个名为MyBlockName的代码块变量类型,它包含两个双浮点类型的参数,并且返回一个双浮点类型的数值。
有了typedef,就可以像下面这样使用这个代码块变量。
MyBlockName *myBlock = ^(double a, double b)
return a * b;
;
NSLog(@“%f, %f”, myBlock (2, 4 ) , myBlock (3, 4) );
4. 代码块和变量
代码块被声明后会捕捉创建点时的状态。
4.1 本地变量
typedef double (^ MyBlock)(void);
double a = 10, b = 20;
MyBlock myBlock = ^(void)
return a * b;
;
a = 30;
b = 20;
NSLog(@“%f”,myBlock());
这段代码最后输出地的是100,而不是600。因为变量是本地变量,代码块会在定义的时候复制并保存它们的状态。
全局变量、参数变量与函数中表现相同。
4.2 _block变量
本地变量会被代码块作为常量获取到。如果你想要修改他们的值,必须将他们声明为可修改的,否则像下面这个实例,编译时会出现错误。
double c = 3;
MyBlock myBlock = ^(double a, double b)
c = a * b;
;
编译器会报这个错误:
Variable is not assignable (missing __block type specifier)
想要修复这个编译错误,需要将变量c标记为__block。
__block double c = 3;
MyBlock myBlock = ^(double a, double b)
c = a * b;
;
有些变量是无法声明为__block类型的。
包括:
1)长度可变的数组
2)包含可变长度数组的结构体
14.2 并发性
GCD技术
苹果公司为了减轻在多核上变成的负担,引入了Grand Central Dispatch,我们称之为GCD。
- GCD技术减少了不少线程管理的麻烦,如果要使用GCD,你需要提交代码块或者函数作为线程来运行。
- GCD是一个系统级别(system-level)的技术,因此你可以在任意级别的代码中使用它。
- GCD决定需要多少线程来安排他们运行的进度。
- 因为GCD是运行在系统级别的,所以可以平衡应用程序所有内容的加载,这样可以提高计算机或设备的执行效率。
14.2.1 同步
Objective-C提供了一个语言级别的(language-level)关键字@synchronized。这个关键字拥有一个参数,通常这个对象是可以修改的。
@synchronized(theObject)
//Critical section
它可以确保不同的线程会连续地访问临界区的代码。
nonatomic属性
如果你定义了一个属性,并且没有指定关键字nonatomic作为属性的特性,编译器会生成强制彼此互斥的getter和setter方法,但是这样设置代码和变量,会产生一些消耗,比直接访问慢一些。为了提高性能,可以添加nonatomic特性。
1. 选择性能
NSObject提供方法以供一些代码只在后台执行。这些方法中都有performSelector:,最简单的就是performSelectorInBackground:WithObject:,它能在后台执行一个方法。它通过创建一个线程来运行方法。定义这些方法时必须遵从以下限制:
1)这些方法运行在各自的线程里,因此你必须为这些Cocoa对象创建一个自动释放池,而主自动释放池是与主线程相关的。
2)这些方法不能有返回值,并且要么没有参数,要么只有一个参数对象。换句话说,你只能使用以下代码格式中的一种:
-(void)myMethod;
-(void)myMethod:(id)myObject;
-(void)myBackgroundMethod:(id)myObject
@autoreleasepool
NSLog(@“My Background Method %@”,myObject);
在后台执行你的方法
[self performSelectorInBackground:@selector(myBackgroundMethod) withObject:argumentObjectl];
当方法执行结束之后,Objective-C运行时会特地清理并弃掉线程。需要注意:方法执行结束后并不会通知你,这是比较简单的代码。如果想要做一些更复杂的事情,需要学习调度队列。
14.2.2 调度队列
GCD可以使用调度队列(dispatch queue),只需写下你的代码,把它指派为一个队列,系统就会执行它了。可以同步或异步执行任意代码。
有三种类型的队列:
1)连续队列:每个连续队列都会根据指派的顺序执行任务。可以按自己的想法创建任意数量的队列,他们会并行操作任务。
2)并发队列:每个并发队列都能并发执行一个或多个任务。任务会根据指派到队列的顺序开始执行。你无法创建并发队列,只能从系统提供的三个队列内选择一个来使用。
3)主队列:它是应用程序中有效的主队列,执行的是应用程序的主线程任务。
连续队列
当有一连串任务需要按照一定顺序执行的时候,可以使用连续队列。任务执行顺序为先进先出(FIFO):只要任务是异步提交的,队列会确保任务根据预定顺序执行。这些队列都是不会发生死锁的。
使用:
dispatch_queue_t my_serial_queue;
my_serial_queue = dispatch_queue_create(“com.appress.MySerialQueue1”,NULL);
第一个参数是队列的名称,第二个参数负责提供队列的特性(现在用不到,所以必须为NULL)。当队列创建好以后,就可以给他指派任务。
并发队列
并发调度队列适合那些可以并行执行的任务。并发队列也遵从先进先出(FIFO)的规范,且任务可以在前一个任务结束前就开始执行。每一次运行同一个程序,并发任务的数量可能是不一样的,因为它会根据其它运行的任务在不同时间变化。
说明:如果需要确保每次运行的任务数量都是一样的,可以通过线程API来手动管理线程。
三种并发队列:
(1)高优先级(high):优先级选项是DISPATCH_QUEUE_PRIORITY_HIGH
(2)默认优先级(default):优先级选项是DISPATCH_QUEUE_PRIORITY_DEFAULT
(3)低优先级(low):优先级选项是DISPATCH_QUEUE_PRIORITY_LOW
如果想要引用他们,可以调用dispatch_get_global_queue方法。
dispatch_queue_t myQueue;
myQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
说明:第一个参数是优先级选项,对应不同的优先级。第二个参数暂时都用0。因为它们都是全局的,所以无需为他们管理内存。不需要保留这些队列的引用,在需要的时候使用函数来访问就行了。
主队列
使用dispatch_get_main_queue可以访问与应用程序主线程相关的连续队列。
dispatch_queue_t main_queue = dispatch_get_current_queue(void);
因为这个队列与主线程相关,所以必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。通常要以同步的方式使用这个队列,提交过个任务并在它们操作完毕后执行一些动作。
获取当前队列
可以通过dispatch_get_current_queue()来找出当前运行的队列代码块。如果在代码块对象之外调用了这个函数,则它将返回主队列。
dispatch_queue_t myQueue = dispatch_get_current_queue();
调度程序
(1) 通过代码块添加任务
代码块必须是dispatch_block_t这样的类型,要定义为没有参数和返回值才行。
typedef void(^dispatch_block_t)(void);
下面的示例添加异步代码块。这个函数拥有两个参数,分别是队列和代码块。
dispatch_async(_serial_queue, ^
NSLog(@“Serial Task 1”);
);
如果是同步添加,使用dispatch_sync函数。
(2) 通过函数添加任务
函数的标准原型必须要像下面这样:
void fucntion_name(void argument)
示例:
void myDispatchFunction(void *argument)
NSLog(@“Serial Task %@”,(__bridge NSNumber *)argument);
NSMutableDictionary *context = (__bridge NSMutableDictionary *)
dispatch_get_context(dispatch_get_current_queue());
NSNumber *value = [context objectForKey:@“value”];
NSLog(@“value = %@“,value);
向队列添加这个函数
调用函数拥有三个参数:队列、需要传递的任意上下文以及函数。如果没有信息要发送给函数,也可以只传递一个NULL值。
dispatch_async_f(_serial_queue, (__bridge void *) [NSNumber numberWithInt:3],
(dispatch_function_t)myDispatchFunction);
如果想以同步的方式添加到队列中,请调用dispatch_sync_f函数。
暂停队列
如果出于某个原因要暂停队列,请调用dispatch_susend()函数并传递队列名称。
dispatch_suspend(_serial_queue);
重新启用队列
队列暂停之后,可以调用dispatch_resume()函数来重新启用。
dispatch_resume(_serial_queue);
14.2.3 操作队列
Objective-C提供一些被称为操作(operation)的API,使队列在Objective-C层级上使用起来更加简单。
如果想要使用操作,首先需要创建一个操作对象,然后将其指派给操作队列,并让队列执行它。一共有三种创建队列的方式。
(1)NSInvocationOperation
如果已经有一个可以完成工作的类,并且想要在队列上执行它,可以尝试使用这种方法。
(2)NSBlockOperation
类似于包含了需要执行代码块的dispatch_async函数。
(3)自定义操作
如果需要更灵活的操作类型,可以创建自己的自定义类型。必须通过NSOperation子类来定义你的操作。
创建调用操作(invocation operation)
@implementation MyCustomClass
-(NSOperation *)operationWithData:(id)data
return [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myWorkerMethod:) object:data];
//This is the method that does the actual work
-(void)myWorkerMethod:(id)data
NSLog(@“My Worker Method %@“,data);
@end
一旦向队列中添加了操作,任务即将执行时便会调用类里面的myWorkerMethod:方法。
创建代码块操作 (block operation)
如果你有一个需要执行的代码块,那么可以创建这个操作并让队列执行它。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWihBlock:^
//Do my work
];
一旦创建了第一个代码块,你便可以通过addExecutionBlock:方法继续添加更多的代码块。根据队列的类型(连续的还是并发的),代码块会分别以连续或者并发的方式进行。
[blockOperation addExecutionBlock:^
//do some more work
];
向队列中添加操作
一旦创建了操作,你就需要向队列中添加代码块。NSOperationQueue一般会并发执行。它具有相关性,因此如果某操作是基于其他操作的,它们会相应地执行。
如果要确保你的操作是连续执行的,可以设置最大并发操作数是1,这样任务就会按照先入先出的规范执行。在向队列添加操作之前,需要某个方法来引用到那个队列。可以创建一个新队列或使用之前已经定义过的队列(比如当前运行的队列)。
NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];
或主队列:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
以下就是创建队列的代码:
NSOperationQueue *_operationQueue = [[NSOperationQueue alloc] init];
添加操作
[_operationQueue addOperation:blockOperation];
也可以添加需要执行的代码块来替代操作对象
[_operationQueue addOperationWithBlock:^
NSLog(“My Block”);
];
一旦队列中添加了操作,它就会被安排进度并执行。
以上是关于代码块和并发性的主要内容,如果未能解决你的问题,请参考以下文章