iOS开发多线程篇—NSOperation简单介绍

来源:互联网 发布:中国第一代程序员 编辑:程序博客网 时间:2024/05/21 18:33

一、NSOperation简介

1.简单说明

NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现多线程编程

NSOperation和NSOperationQueue实现多线程的具体步骤:

(1)先将需要执行的操作封装到一个NSOperation对象中

(2)然后将NSOperation对象添加到NSOperationQueue中

(3)系统会⾃动将NSOperationQueue中的NSOperation取出来

(4)将取出的NSOperation封装的操作放到⼀条新线程中执⾏

 2.NSOperation的子类

NSOperation是个抽象类,并不具备封装操作的能力,必须使⽤它的子类

使用NSOperation⼦类的方式有3种:

(1)NSInvocationOperation

(2)NSBlockOperation

(3)自定义子类继承NSOperation,实现内部相应的⽅法

二、 具体说明

1.NSInvocationOperation子类

创建对象和执行操作:

//创建操作对象,封装要执行的任务    //NSInvocationOperation   封装操作    NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];        //执行操作    [operation start];

说明:一旦执⾏操作,就会调用target的test方法

代码示例:

#import "YYViewController.h"@implementation YYViewController- (void)viewDidLoad{    [super viewDidLoad];        //NSOperation:抽象类,不具备封装功能        //创建操作对象,封装要执行的任务    //NSInvocationOperation   封装操作    NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];        //执行操作    [operation start];}-(void)test{    NSLog(@"--test--%@--",[NSThread currentThread]);}@end

打印查看:


注意:操作对象默认在主线程中执行,只有添加到队列中才会开启新的线程。即默认情况下,如果操作没有放到队列中queue中,都是同步执行。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作 

2.NSBlockOperation子类

创建对象和添加操作:

//创建NSBlockOperation操作对象    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{        //......    }];        //添加操作    [operation addExecutionBlock:^{        //....    }];

代码示例:

代码1:

#import "YYViewController.h"@implementation YYViewController- (void)viewDidLoad{    [super viewDidLoad];        //创建NSBlockOperation操作对象    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{        NSLog(@"NSBlockOperation------%@",[NSThread currentThread]);    }];        //开启执行操作    [operation start];}@end

打印查看:


代码2

#import "YYViewController.h"@implementation YYViewController- (void)viewDidLoad{    [super viewDidLoad];        //创建NSBlockOperation操作对象    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{        NSLog(@"NSBlockOperation------%@",[NSThread currentThread]);    }];        //添加操作    [operation addExecutionBlock:^{        NSLog(@"NSBlockOperation1------%@",[NSThread currentThread]);    }];        [operation addExecutionBlock:^{        NSLog(@"NSBlockOperation2------%@",[NSThread currentThread]);    }];        //开启执行操作    [operation start];}@end


注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作 

3.NSOperationQueue

NSOperationQueue的作⽤:NSOperation可以调⽤start⽅法来执⾏任务,但默认是同步执行的

如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

添加操作到NSOperationQueue中,自动执行操作,自动开启线程

//创建NSOperationQueue    NSOperationQueue * queue=[[NSOperationQueue alloc]init];    //把操作添加到队列中    //第一种方式    [queue addOperation:operation1];    [queue addOperation:operation2];    [queue addOperation:operation3];    //第二种方式    [queue addOperationWithBlock:^{        NSLog(@"NSBlockOperation3--4----%@",[NSThread currentThread]);    }];

- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block; 

代码示例:

#import "YYViewController.h"@implementation YYViewController- (void)viewDidLoad{    [super viewDidLoad];    //创建NSInvocationOperation对象,封装操作    NSInvocationOperation *operation1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test1) object:nil];    NSInvocationOperation *operation2=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];    //创建对象,封装操作    NSBlockOperation *operation3=[NSBlockOperation blockOperationWithBlock:^{        NSLog(@"NSBlockOperation3--1----%@",[NSThread currentThread]);    }];    [operation3 addExecutionBlock:^{        NSLog(@"NSBlockOperation3--2----%@",[NSThread currentThread]);    }];        //创建NSOperationQueue    NSOperationQueue * queue=[[NSOperationQueue alloc]init];    //把操作添加到队列中    [queue addOperation:operation1];    [queue addOperation:operation2];    [queue addOperation:operation3];}-(void)test1{    NSLog(@"NSInvocationOperation--test1--%@",[NSThread currentThread]);}-(void)test2{    NSLog(@"NSInvocationOperation--test2--%@",[NSThread currentThread]);}@end

打印效果:


注意:系统自动将NSOperationqueue中的NSOperation对象取出,将其封装的操作放到一条新的线程中执行。上面的代码示例中,一共有四个任务,operation1和operation2分别有一个任务,operation3有两个任务。一共四个任务,开启了四条线程。通过任务执行的时间全部都是273可以看出,这些任务是并行执行的。

提示:队列的取出是有顺序的,与打印结果并不矛盾。这就好比,选手A,BC虽然起跑的顺序是先A,后B,然后C,但是到达终点的顺序却不一定是A,B在前,C在后。

下面使用for循环打印,可以更明显的看出任务是并发执行的。

代码示例:

#import "YYViewController.h"@implementation YYViewController- (void)viewDidLoad{    [super viewDidLoad];    //创建NSInvocationOperation对象,封装操作    NSInvocationOperation *operation1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test1) object:nil];    NSInvocationOperation *operation2=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];    //创建对象,封装操作    NSBlockOperation *operation3=[NSBlockOperation blockOperationWithBlock:^{        for (int i=0; i<5; i++) {            NSLog(@"NSBlockOperation3--1----%@",[NSThread currentThread]);        }    }];    [operation3 addExecutionBlock:^{        for (int i=0; i<5; i++) {        NSLog(@"NSBlockOperation3--2----%@",[NSThread currentThread]);        }    }];        //创建NSOperationQueue    NSOperationQueue * queue=[[NSOperationQueue alloc]init];    //把操作添加到队列中    [queue addOperation:operation1];    [queue addOperation:operation2];    [queue addOperation:operation3];}-(void)test1{    for (int i=0; i<5; i++) {    NSLog(@"NSInvocationOperation--test1--%@",[NSThread currentThread]);    }}-(void)test2{    for (int i=0; i<5; i++) {    NSLog(@"NSInvocationOperation--test2--%@",[NSThread currentThread]);    }}@end




NSOperation和NSOperationQueue的使

NSOperation API
NSOperation 类有一个相当简短的声明。要定制一个操作,可以遵循以下步骤:

1.继承NSOperation类
2.如果你需要传递一些值和指针到一个线程中,创建你自己的指定初始化方法是一个很好的尝试
3.重写“main”方法,在“main”方法中一定创建一个“autoreleasepool”将你的代码放在“autoreleasepool”中创建你自己的自动释放池的原因是,你不能访问主线程的自动释放池,所以你应该自己创建一个。
以下是一个例子:

#import 


@interface MyOperation : NSOperation{

    

}

@property(retainnonatomic)    NSString  *picIdString; //图片id

@property(retain, nonatomic)    NSObject *mainTarget; //实现类


//重写初始化方法,传递参进来

-(id)initWithPicId:(NSString *)string target:(id)target;


@end


#import "MyOperation.h"

#import "MyObject.h"


@implementation MyOperation

@synthesize picIdString;

@synthesize mainTarget;


-(id)initWithPicId:(NSString *)string target:(id)target{

    self=[super init];

    if (self) {

        self.picIdString=string;

        self.mainTarget=(NSObject *)target;

    }

    return self;

}


- (void)main {

    @autoreleasepool {

  //要经常检查isCancelled属性。如果操作不需要被执行了,你就不想在后台去运行它了        

  // 这个操作是否取消?
         if (self.isCancelled)
            return;


        if ([MyData fileExists:[NSString stringWithFormat:@"bigImage/%@",self.picIdString]]) {

            [mainTarget performSelectorOnMainThread:@selector(imageFiltrationDidFinish)

                                         withObject:nil

                                      waitUntilDone:YES];

            return;

        }

                

        //下载图片

        NSData *data_=[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:K_bigImageURL(self.picIdString)]];

        [MyData saveDataToDocumentsWhiteData:data_

                                    dataName:self.picIdString

                                    flieName:@"bigImage"];        

        [data_ release]; data_=nil;

        


 // 这个操作是否取消?
         if (self.isCancelled)
           return;


        [self.mainTarget performSelectorOnMainThread:@selector(imageFiltrationDidFinish)

                                          withObject:nil

                                       waitUntilDone:YES];

    }

}


- (void)dealloc

{

    [picIdString release];picIdString=nil;

    [mainTarget release]; mainTarget=nil;

    [super dealloc];

}


@end


开始(start):通常,你不会重写这个方法。重写“start”方法需要相对复杂的实现,你还需要注意像isExecuting,isFinished,isConcurrent和isReady这些属性。当你将一个操作添加到一个队列当中时(一个NSOperationQueue的实例,接下来会讨论的),这个队列会在操作中调用“start”方法,然后它会做一些准备和“main”方法的后续操作。假如你在一个NSOperation实例中调用了“start”方法,如果没有把它添加到一个队列中,这个操作会在main loop中执行。

从属性(Dependency):你可以让一个操作从属于其他的操作。任何操作都可以从属于任意数量的操作。当你让操作A从属于操作B时,即使你调用了操作A的“start”方法,它会等待操作B结束后才开始执行。例如:

MyDownloadOperation *downloadOp = [[MyDownloadOperation alloc] init]; // MyDownloadOperation is a subclass of NSOperation
MyFilterOperation *filterOp = [[MyFilterOperation alloc] init]; // MyFilterOperation  is a subclass of NSOperation
         
[filterOp addDependency:downloadOp];

要删除依赖性:
[filterOp removeDependency:downloadOp];

优先级(Priority):有时候你希望在后台运行的操作并不是很重要的,它可以以较低的优先级执行。可以通过使用“setQueuePriority:”方法设置一个操作的优先级。
 
[filterOp setQueuePriority:NSOperationQueuePriorityVeryLow]; 

其他关于设置线程优先级的选择有: NSOperationQueuePriorityLow, NSOperationQueuePriorityNormal, NSOperationQueuePriorityHigh和NSOperationQueuePriorityVeryHigh.

当你添加了操作到一个队列时,在对操作调用“start”方法之前,NSOperationQueue会浏览所有的操作。那些有较高优先级的操作会被先执行。有同等优先级的操作会按照添加到队列中的顺序去执行(先进先出)。

(历史注释:在1997年,火星车中的嵌入式系统遭遇过优先级反转问题,也许这是说明正确处理优先级和互斥锁的最昂贵示例了。

Completion block:在NSOperation 类中另一个有用的方法叫setCompletionBlock:。一旦操作完成了,如果你还有一些事情想做,你可以把它放在一个块中,并且传递给这个方法。这个块会在主线程中执行。 

你并不需要重写“start”方法。然而,如果你决定去重写“start”方法,就必须处理好像isExecuting,isFinished,isConcurrent和isReady这些属性。否则你的操作类不会正确的运作。

你一旦添加了一个操作到一个队列(NSOperationQueue的一个实例)中,就要负责释放它(如果你不使用ARC的话)。NSOperationQueue获得操作对象的所有权,调用“start”方法,然后结束时负责释放它。

你不能重用一个操作对象。一旦它被添加到一个队列中,你就丧失了对它的所有权。如果你想再使用同一个操作类,就必须创建一个新的实例变量。

一个结束的操作不能被重启。

如果你取消了一个操作,它不会马上就发生。它会在未来的某个时候某人在“main”函数中明确地检查isCancelled==YES时被取消掉;否则,操作会一直执行到完成为止。

一个操作是否成功地完成,失败了,或者是被取消了,isFinished的值总会被设置为YES。所以千万不要觉得isFinished==YES就表示所有的事情都顺利完成了—特别的,如果你在代码里面有从属性(dependencies),就要更加注意!



NSOperationQueue API

NSOperationQueue 也有一个相当简单的界面。它甚至比NSOperation还要简单,因为你不需要去继承它,或者重写任何的方法 — 你可以简单创建一个。给你的队列起一个名字会是一个不错的做法;这样你可以在运行时识别出你的操作队列,并且让调试变得更简单:


NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
myQueue.name = @"Download Queue";

并发操作:队列和线程是两个不同的概念。一个队列可以有多个线程。每个队列中的操作会在所属的线程中运行。举个例子你创建一个队列,然后添加三个操作到里面。队列会发起三个单独的线程,然后让所有操作在各自的线程中并发运行。

到底有多少个线程会被创建?这是个很好的问题!这取决与硬件。默认情况下,NSOperationQueue类会在场景背后施展一些魔法,决定如何在特定的平台下运行代码是最好的,并且会尽量启用最大的线程数量。考虑以下的例子。假设系统是空闲的,并且有很多的可用资源,这样NSOperationQueue会启用比如8个同步线程。下次你运行程序,系统会忙于处理其他不相关的操作,它们消耗着资源,然后NSOperationQueue只会启用两个同步线程了。

并发操作的最大值:你可以设定NSOperationQueue可以并发运行的最大操作数。NSOperationQueue会选择去运行任何数量的并发操作,但是不会超过最大值。

myQueue.MaxConcurrentOperationCount = 3;

如果你改变了主意,想将MaxConcurrentOperationCount设置回默认值,你可以执行下列操作:

myQueue.MaxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; 

添加操作:一个操作一旦被添加到一个队列中,你就应该通过传送一个release消息给操作对象(如果使用了手动引用计数,非ARC的话),然后队列会负责开始这个操作。从这点上看,什么时候调用“start”方法由这个队列说了算。 
[myQueue addOperation:downloadOp]; 
[downloadOp release]; // manual reference counting 

待处理的操作:任何时候你可以询问一个队列哪个操作在里面,并且总共有多少个操作在里面。记住只有那些等待被执行的操作,还有那些正在运行的操作,会被保留在队列中。操作一完成,就会退出队列。 
NSArray *active_and_pending_operations = myQueue.operations;
NSInteger count_of_operations = myQueue.operationCount; 

暂停队列:你可以通过设定setSuspended:YES来暂停一个队列。这样会暂停所有在队列中的操作 — 你不能单独的暂停操作。要重新开始队列,只要简单的setSuspended:NO。 
// Suspend a queue
[myQueue setSuspended:YES];

// Resume a queue
[myQueue setSuspended: NO]; 

取消操作:要取消一个队列中的所有操作,你只要简单的调用“cancelAllOperations”方法即可。还记得之前提醒过经常检查NSOperation中的isCancelled属性吗?

原因是“cancelAllOperations”并没有做太多的工作,他只是对队列中的每一个操作调用“cancel”方法 — 这并没有起很大作用!:] 如果一个操作并没有开始,然后你对它调用“cancel”方法,操作会被取消,并从队列中移除。然而,如果一个操作已经在执行了,这就要由单独的操作去识别撤销(通过检查isCancelled属性)然后停止它所做的工作。

[myQueue cancelAllOperations];

addOperationWithBlock: 如果你有一个简单的操作不需要被继承,你可以将它当做一个块(block)传递给队列。如果你需要从块那里传递回任何数据,记得你不应该传递任何强引用的指针给块;相反,你必须使用弱引用。而且,如果你想要在块中做一些跟UI有关的事情,你必须在主线程中做。

UIImage *myImage = nil;
 
// Create a weak reference
__weak UIImage *myImage_weak = myImage;
 
// Add an operation as a block to a queue
[myQueue addOperationWithBlock: ^ {
 
    // a block of operation
    NSURL *aURL = [NSURL URLWithString:@"http://www.somewhere.com/image.png"];
    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfURL:aURL options:nil error:&error];
    If (!error)
        [myImage_weak imageWithData:data];
 
    // Get hold of main queue (main thread)
    [[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
        myImageView.image = myImage_weak; // updating UI
    }];
 
}]; 


0 0