iOS7 Programming Cookbook-Chapter 7-Concurrency(Operation)

来源:互联网 发布:查看端口占用命令 编辑:程序博客网 时间:2024/05/22 03:19

本文是笔者自己自学iOS7开发的过程中,练习翻译的《iOS7 Programming Cookbook》,兼做个人的笔记和代码汇总。

由于第七章篇幅过长,拆分成四部分,这是其中的Operation部分。

7.11 Running Tasks Synchronously with Operations

问题:

你希望同步运行一些列任务

解决方法:

创建operation,并手动启动它们:

@interface AppDelegate ()@property (nonatomic, strong) NSInvocationOperation *simpleOperation;@end
实现文件如下:
- (void) simpleOperationEntry:(id)paramObject{    NSLog(@"Parameter Object = %@", paramObject);    NSLog(@"Main Thread = %@", [NSThread mainThread]);    NSLog(@"Current Thread = %@", [NSThread currentThread]);}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{        NSNumber *simpleObject = [NSNumber numberWithInteger:123];    self.simpleOperation = [[NSInvocationOperation alloc]                            initWithTarget:self                            selector:@selector(simpleOperationEntry:) object:simpleObject];    [self.simpleOperation start];    self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];    return YES;}
如出结果如下:

    Parameter Object = 123    Main Thread = <NSThread: 0x6810280>{name = (null), num = 1}    Current Thread = <NSThread: 0x6810280>{name = (null), num = 1}
和类名字NSInvocation暗示的一样,该类实体的主要任务就是在实体内执行一个方法。这是在对象内使用一个方法的最直接办法。

讨论

operation能够在一个实例内使用一个方法。那么你可能会想这种方法有什么特别的呢?operation的威力会在加入进一个队列中后展现出来,有了operation队列,你可以在一个目标实例中异步执行方法。下面我们将学习如何利用operation队列来异步运行任务。

除了operation之外,你可以使用block或者普通的operation来同步执行任务。下面是一个使用block来从0至999计数的例子:

@interface AppDelegate ()@property (nonatomic, strong) NSBlockOperation *simpleOperation; @end@implementation AppDelegate
下面是AppDelegate的实现部分:
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.simpleOperation = [NSBlockOperation blockOperationWithBlock:^{        NSLog(@"Main Thread = %@", [NSThread mainThread]);        NSLog(@"Current Thread = %@", [NSThread currentThread]);        NSUInteger counter = 0;        for (counter = 0; counter < 1000;counter++){            NSLog(@"Count = %lu", (unsigned long)counter);        }    }];    /* Start the operation */    [self.simpleOperation start];    /* Print something out just to test if we have to wait     for the block to execute its code or not */    NSLog(@"Main thread is here");    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];    return YES;}
如果我们运行代码,会看到如下输出:
 Main Thread = <NSThread: 0x6810280>{name = (null), num = 1}    Current Thread = <NSThread: 0x6810280>{name = (null), num = 1}    ...    Count = 991
 Count = 992    Count = 993    Count = 994    Count = 995    Count = 996    Count = 997    Count = 998    Count = 999    Main thread is here

这证明了block在application:didFinishLauchingWithOptions:方法中开始,而该方法在主线程中运行,block中的代码也在主线程中运行。从输出信息中看出的要点是operation阻塞了主线程,主线程的代码在block operation结束后继续运行。这是个很糟的变成练习。事实上,iOS开发者必须学着使用他们所知道的所有技术使主线程保持可响应,以便可以保持对用户输入的处理。

想要阅读更多这个主题可以阅读iOS参考库中的“Performance Tuning”文件。

除了invocation和block operation外,你还可以使用NSOperation的子类来执行你的任务。在开始之前,我们必须记住一些事情:

  • 如果你计划使用operation队列,你必须在operation的start方法中附加一个新线程。如果你不想使用一个operation队列,并且也不希望你的operation和你手动运行的其他operation异步执行,那么你可以在start方法中调用你的operation的main方法。
  • 有两个方法是你的operation中必须重载的,一个是isExecuting,一个是isFinished。这些可以从任一实例中调用。在这些方法中,你必须返回一个线程安全的值,并且你可以在operation可以操作这些值。一旦你的operation开始了,你必须通过KVO通知所有的监听者,你正在改变这两个方法的返回值。我们将在样例代码中看到这是如何运行的。
  • 你必须在main方法内提供你自己的自动释放池,以防你的operation会在以后被加入到一个operation队列中。你必须确保你的operation在两种方式下都可以运作,一种手动,一种由operation队列启动。
  • 你必须有一个初始化方法。每个operation必须只有一个指定的初始化器。所有初始化方法,包括默认地init方法,必须调用有最多参数的初始化器。其他的初始化器方法必须确保他们将恰当的参数传给指定的初始化器。

下面是operation对象的声明:
#import <Foundation/Foundation.h>@interface CountingOperation : NSOperation    /* Designated Initializer */- (instancetype) initWithStartingCount:(NSUInteger)paramStartingCount endingCount:(NSUInteger)paramEndingCount;@end
实现文件稍微有点长,但是很容易理解:
#import "CountingOperation.h"@interface CountingOperation ()@property (nonatomic, unsafe_unretained) NSUInteger startingCount;@property (nonatomic, unsafe_unretained) NSUInteger endingCount;@property (nonatomic, unsafe_unretained, getter=isFinished) BOOL finished;@property (nonatomic, unsafe_unretained, getter=isExecuting) BOOL executing;@end@implementation CountingOperation-(instancetype)init{    return [self initWithStartingCount:0 endingCount:1000];}-(instancetype)initWithStartingCount:(NSUInteger)paramStartingCount                         endingCount:(NSUInteger)paramEndingCount{    self=[super init];    if(self!=nil){            _startingCount=paramStartingCount;        _endingCount=paramEndingCount;    }    return (self);}- (void) main {    @try {        /* Here is our autorelease pool */ @autoreleasepool {            /* Keep a local variable here that must get set to YES             whenever we are done with the task */            BOOL taskIsFinished = NO;            /* Create a while loop here that only exists             if the taskIsFinished variable is set to YES or             the operation has been cancelled */            while (taskIsFinished == NO && [self isCancelled] == NO){                /* Perform the task here */                NSLog(@"Main Thread = %@", [NSThread mainThread]);                NSLog(@"Current Thread = %@", [NSThread currentThread]);                NSUInteger counter = _startingCount;                for (counter = _startingCount;counter < _endingCount;counter++){                    NSLog(@"Count = %lu", (unsigned long)counter);                }                /* Very important. This way we can get out of the                 loop and we are still complying with the cancellation                 rules of operations */                taskIsFinished = YES;            }            /* KVO compliance. Generate the             required KVO notifications */            [self willChangeValueForKey:@"isFinished"];            [self willChangeValueForKey:@"isExecuting"];            _finished = YES;            _executing = NO;            [self didChangeValueForKey:@"isFinished"];            [self didChangeValueForKey:@"isExecuting"];        }    }    @catch (NSException * e) {        NSLog(@"Exception %@", e);    }}@end

我们可以这样运行operation:

@interface AppDelegate ()@property (nonatomic, strong) CountingOperation *simpleOperation;@end@implementation AppDelegate- (BOOL) application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.simpleOperation = [[CountingOperation alloc]                            initWithStartingCount:0                            endingCount:1000];    [self.simpleOperation start];    NSLog(@"Main thread is here");    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];    return YES;}

如果我们运行这段代码,我们可以在输出窗口看到如下输出:

 Main Thread = <NSThread: 0x6810260>{name = (null), num = 1}    Current Thread = <NSThread: 0x6810260>{name = (null), num = 1}    ...    Count = 993
    Count = 994    Count = 995    Count = 996    Count = 997    Count = 998    Count = 999    Main thread is here


7.12 Running Tasks Asynchronously with Operations

问题:你希望同时执行operations

方法:

这就需要使用operation队列了。

讨论:

通常我们在主线程中开始operation,但是同时我们希望其他的operation运行在他们自己的线程上,而且不会占据主线程的时间片,对我们来说最好的解决方案就是使用operation队列。然而如果我们希望手动管理你的operation,你可以继承一个NSOperation,然后再main方法上附加一个新线程。

然我们继续使用一个operation队列,并且将两个operation加入其中。下面是声明文件和使用operation队列:

@interface AppDelegate ()@property (nonatomic, strong) NSInvocationOperation *firstOperation;@property (nonatomic, strong) NSInvocationOperation *secondOperation;@property(nonatomic,strong) NSOperationQueue* operationQueue;@end
-(void)firstOperationEntry:(id)paramObject{    NSLog(@"%s",__FUNCTION__);    NSLog(@"Parameter Object = %@", paramObject);    NSLog(@"Main Thread = %@", [NSThread mainThread]);    NSLog(@"Current Thread = %@", [NSThread currentThread]);}-(void)secondOperationEntry:(id)paramObject{    NSLog(@"%s",__FUNCTION__);    NSLog(@"Parameter Object = %@", paramObject);    NSLog(@"Main Thread = %@", [NSThread mainThread]);    NSLog(@"Current Thread = %@", [NSThread currentThread]);}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    NSNumber *firstNumber = @111;    NSNumber *secondNumber = @222;        self.firstOperation =[[NSInvocationOperation alloc]                         initWithTarget:self                         selector:@selector(firstOperationEntry:) object:firstNumber];    self.secondOperation = [[NSInvocationOperation alloc]                            initWithTarget:self                            selector:@selector(secondOperationEntry:) object:secondNumber];    self.operationQueue=[[NSOperationQueue alloc]init];    [self.operationQueue addOperation:self.firstOperation];    [self.operationQueue addOperation:self.secondOperation];            NSLog(@"Main thread is here");    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];            return YES;}

下面是实现代码中发生的事情:

  • 我们有两个方法,firstOperation和secondOperation,每个方法接受一个实例作为参数,并打印出当前线程,主线程,还有参数到输出窗口。这些是加入operation队列的operation的入口方法。
  • 我们初始化了两个NSInvocationOperation的实例,并设置了目标选择器如前面描述的那样。
  • 然后我们初始化了NSOperationQueue。这个队列实例将会负责管理operation实例中的同步问题。
  • 我们使用了NSOperationQueue的实例方法addOperation将每个operation加入队列中。有一点你必须记住的事,在将operation加入operation队列中后,你一定不要立即手动启动operation,你必须将这个任务留给operation队列。

运行代码可以看到如下输出:

[Running_Tasks_Asynchronously_with_OperationsAppDelegate firstOperationEntry:]Mainthreadis here
Parameter Object
=111
[Running_Tasks_Asynchronously_with_OperationsAppDelegate secondOperationEntry:]Main Thread = <NSThread:0x6810260>{name=(null),num=1}

    Parameter Object = 222    Current Thread = <NSThread: 0x6805c20>{name = (null), num = 3}    Main Thread = <NSThread: 0x6810260>{name = (null), num = 1}    Current Thread = <NSThread: 0x6b2d1d0>{name = (null), num = 4}

这证明了operation运行在他们自己的线程上,而且与主线程并行。让我们重复运行同样的代码,观察输出。如果你这样做,很可能你会看到不同的结果,像这样:

Main thread is here    [Running_Tasks_Asynchronously_with_OperationsAppDelegate firstOperationEntry:]    [Running_Tasks_Asynchronously_with_OperationsAppDelegate secondOperationEntry:]    Parameter Object = 111    Main Thread = <NSThread: 0x6810260>{name = (null), num = 1}    Current Thread = <NSThread: 0x68247c0>{name = (null), num = 3}    Parameter Object = 222    Main Thread = <NSThread: 0x6810260>{name = (null), num = 1}    Current Thread = <NSThread: 0x6819b00>{name = (null), num = 4}
你可以清楚地看到主线程未被阻塞,并且两个operation都与主线程并行。这证明了operation队列的并行效果。这个队列管理着需要运行operation的线程。

如果我们继承NSOperation,并且向队列中添加新类的实例,那么我们要做的事可能会有点不同。首先请将下面这些记住:

  • 一般继承NSOperation的operation,被加入队列时,会异步执行。因此,你必须重载实例方法isConcurrent返回YES。
  • 你必须让你的operation在运行主任务时周期性检查isCancelled方法的值来判断是否取消,还有在start方法中,甚至在运行operation前就要这样做。start方法会被队列调用在它被添加到队列后。在这个方法中,使用isCancelled方法来检查operation是否被取消。如果operation被取消,就从start方法中返回,如果没有,在start方法中调用main方法。
  • 在你的实现文件中重载main方法,。确保在该方法中分配并初始化你自己的自动释放池。
  • 重载isFinished和isExecuting方法,并返回正确的BOOL值表示operation当时是结束还是运行。

下面是operation的声明文件(.h):

#import <Foundation/Foundation.h>@interface SimpleOperation : NSOperation/* Designated Initializer */- (instancetype) initWithObject:(NSObject *)paramObject;@end
实现文件如下:
#import "SimpleOperation.h"@interface SimpleOperation ()@property (nonatomic, strong) NSObject *givenObject;@property (nonatomic, unsafe_unretained, getter=isFinished) BOOL finished;@property (nonatomic, unsafe_unretained, getter=isExecuting) BOOL executing;@end@implementation SimpleOperation-(instancetype)init{    return [self initWithObject:@"123"];}- (instancetype) initWithObject:(NSObject *)paramObject{    self = [super init];    if (self != nil){        /* Keep these values for the main method */        _givenObject = paramObject;    }    return(self);}-(void)main{    @try {        @autoreleasepool {            /* Keep a local variable here that must get set to YES             whenever we are done with the task */            BOOL taskIsFinished = NO;            /* Create a while loop here that only exists             if the taskIsFinished variable is set to YES or             the operation has been cancelled */            while (taskIsFinished == NO && [self isCancelled] == NO){                /* Perform the task here */                NSLog(@"%s", __FUNCTION__);                NSLog(@"Parameter Object = %@", _givenObject);                NSLog(@"Main Thread = %@", [NSThread mainThread]);                NSLog(@"Current Thread = %@", [NSThread currentThread]);                /* Very important. This way we can get out of the                 loop and we are still complying with the cancellation                 rules of operations */                taskIsFinished = YES;            }            /* KVO compliance. Generate the             required KVO notifications */            [self willChangeValueForKey:@"isFinished"];            [self willChangeValueForKey:@"isExecuting"];            _finished = YES;            _executing = NO;            [self didChangeValueForKey:@"isFinished"];            [self didChangeValueForKey:@"isExecuting"];        }    }    @catch (NSException *e) {        NSLog(@"Exception %@",e);    }}- (BOOL) isConcurrent{    return YES;}@end
下面是应用代理使用这个新的operation并且把它加入一个operation队列的声明:
@interface AppDelegate ()@property (nonatomic, strong) NSOperationQueue *operationQueue;@property (nonatomic, strong) SimpleOperation *firstOperation;@property (nonatomic, strong) SimpleOperation *secondOperation;@end
实现文件如下:
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    NSNumber *firstNumber = @111;    NSNumber *secondNumber = @222;    self.firstOperation = [[SimpleOperation alloc]                           initWithObject:firstNumber];    self.secondOperation = [[SimpleOperation alloc]                            initWithObject:secondNumber];    self.operationQueue = [[NSOperationQueue alloc] init];    /* Add the operations to the queue */    [self.operationQueue addOperation:self.firstOperation];    [self.operationQueue addOperation:self.secondOperation];    NSLog(@"Main thread is here");    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];    return YES;}
输出结果与我们之前看到的同步invocation operation的结果相似:
Main thread is here    -[SimpleOperation main]    -[SimpleOperation main]    Parameter Object = 222    Parameter Object = 222    Main Thread = <NSThread: 0x6810260>{name = (null), num = 1}    Main Thread = <NSThread: 0x6810260>{name = (null), num = 1}    Current Thread = <NSThread: 0x6a10b90>{name = (null), num = 3}    Current Thread = <NSThread: 0x6a13f50>{name = (null), num = 4}


7.13 Creating Dependency Between Operations

问题:你希望只有当另一任务完成时才执行某一特定任务

解决方案:

如果operation B必须等待operation A,那么operation B必须使用NSOperation的实例方法addDependency添加operation A作为它的依赖对象。像这样:

    [self.firstOperation addDependency:self.secondOperation];
firstOperation和secondOperation都是NSInvocationOperation的属性。在样例代码中,第一个operation在第二个operation的任务完成后才会执行。

讨论:

一个operation必须在所有它以来的operation成功完成后才会开始。默认地,在初始化后,operation不依赖于任何operation。

样例代码如下:

@interface AppDelegate ()@property (nonatomic, strong) NSInvocationOperation *firstOperation;@property (nonatomic, strong) NSInvocationOperation *secondOperation;@property (nonatomic, strong) NSOperationQueue *operationQueue;@end

-(void)firstOperationEntry:(id)paramObject{    NSLog(@"First Operation - Parameter Object = %@",          paramObject);    NSLog(@"First Operation - Main Thread = %@",          [NSThread mainThread]);    NSLog(@"First Operation - Current Thread = %@",          [NSThread currentThread]);}-(void)secondOperationEntry:(id)paramObject{    NSLog(@"Second Operation - Parameter Object = %@",          paramObject);    NSLog(@"Second Operation - Main Thread = %@",          [NSThread mainThread]);    NSLog(@"Second Operation - Current Thread = %@",          [NSThread currentThread]);}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    NSNumber *firstNumber = @111;    NSNumber *secondNumber = @222;        self.firstOperation =[[NSInvocationOperation alloc]                         initWithTarget:self                         selector:@selector(firstOperationEntry:) object:firstNumber];    self.secondOperation = [[NSInvocationOperation alloc]                            initWithTarget:self                            selector:@selector(secondOperationEntry:) object:secondNumber];    [self.firstOperation addDependency:self.secondOperation];    self.operationQueue=[[NSOperationQueue alloc]init];    [self.operationQueue addOperation:self.firstOperation];    [self.operationQueue addOperation:self.secondOperation];            NSLog(@"Main thread is here");    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];            return YES;}

输出结果如下:

 Second Operation - Parameter Object = 222    Main thread is here    Second Operation - Main Thread = <NSThread: 0x6810250>{name = (null), num = 1}    Second Operation - Current Thread = <NSThread: 0x6836ab0>{name = (null), num = 3}    First Operation - Parameter Object = 111    First Operation - Main Thread = <NSThread: 0x6810250>{name = (null), num = 1}    First Operation - Current Thread = <NSThread: 0x6836ab0>{name = (null), num = 3}

很明显,尽管operation队列希望并行执行两个operation,但是第一个operation依赖于第二个operation,因此第二个完成后第一个才能运行。

如果你希望撤销依赖,你可以使用operation的实例方法removeDependency。

0 0