《Objective-C基础教程》第14章 代码块和并发性

来源:互联网 发布:迪杰斯特拉算法伪代码 编辑:程序博客网 时间:2024/06/06 19:12

第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”);}];

一旦队列中添加了操作,它就会被安排进度并执行。