iOS 多线程开发之OperationQueue(一)概念+两种Operation

原创blog,转载请注明出处
blog.csdn.net/hello_hwc
欢迎关注我的iOS SDK详解专栏,这里有很多基础的文章
http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html

前言:在iOS开发 中,多线程是一个很重要的一个方面。iOS的多线程使用可以分为几个方面。由底层到上层分别是

  • pthread
  • NSThread
  • GCD
  • NSOperationQueue

这里不得不提到一点,操作系统是以线程为任务调度基本单位,对于一个单核CPU,实际上是采用时间片轮转来实现多线程的。而对于多核CPU,则可以实现真正的并发执行。

我的iOS SDK讲解系列并不会详细讲解pthread 和 NSThread,二者很少会用到,也很容易在使用时候出问题。

本文会简单讲解下NSOperationQueue以及NSOperation,并且给出一个简单的Demo实现串行和并行的NSOperation

简单介绍下pthread和NSThread

pthread 是Unix层次的线程,相当底层。可以参见WIKI。
NSThread是对pthread的进一步封装,但是仍然在使用的时候出现各种各样的问题。

在iOS开发的过程中,有一点要谨记:

尽可能的使用上层API去实现,除非上层API没办法实现再考虑底层API

NSOperationQueue概述

NSOperationQueue把线程操作抽象成队列操作,它是GCD的上层封装,队列中是一个个的NSOperation,NSOperation用来定义任务的具体执行。
使用NSOperationQueue和NSOperation相关的API可以方便而安全的的进行

  1. 取消一个任务
  2. 添加任务的依赖关系,例如任务3要再任务1和任务2执行完毕才能执行
  3. 设置一个Queue最多同时执行的任务数量。默认情况Queue会做到最优化执行。
  4. KVO监听任务的状态变化

另外,NSOperationQueue也分为两种

  1. mainQueue(主线程),注意只能在主线程处理UI相关。
  2. 自定义Queue(后台线程)。

当把一个NSOperation添加到自定义Queue后,会在当前线程之外的另一个线程来执行操作。

NSOperation 概述

写了个简单的Demo,异步下载3张图片。
- 为下载提供独立的进度条
- 每次队列只允许一个下载任务进行

iOS 多线程开发之OperationQueue(一)概念+两种Operation_第1张图片

下载链接

NSOperation用来封装要实现的任务,注意这些任务是一次性的,不能重复利用。通常执行任务要把Operation添加到OperationQueue。等到任务执行完毕后,Operation会被从Queue上移除并且销毁。

有几点要提到:

  1. NSOperation是一个抽象类,使用的时候要使用它的子类来定义任务
  2. NSOperation是线程安全的,也就是说在不同线程上访问NSOperation对象是安全的。同样,为NSOperation子类添加的方法也要是线程安全的。
  3. NSOperation分为两种,一种是同步的,一种是异步的。这个接下来会详细讲解
  4. NSOperation的很多属性支持KVO
    支持KVO的属性
isCancelled - read-only
isAsynchronous - read-only
isExecuting - read-only
isFinished - read-only
isReady - read-only
dependencies - read-only
queuePriority - readable and writable
completionBlock - readable and writable

同步的NSOperation

注意,这里的同步异步是相对任务本身的,同步的任务指的是start-main-结束,main结束后,则任务结束。异步的任务例如有网络请求的,main函数结束了,任务还没结束。

注意:

  • init和start函数都是在创建NSOperation线程上执行的。
  • main是在后台线程上执行的。

对于同步的NSOperation来说,只需要重写main函数,保证main函数里的任务是同步执行的。
例如用NSOperationQueue下载一副图片。
头文件
我们传入imageview,task负责更新UI

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SycDownloadImageOperation : NSOperation
-(instancetype)initWithURL:(NSURL *)url ImageView:(UIImageView *)imageview;
@end

.m文件

#import "SycDownloadImageOperation.h"
@interface SycDownloadImageOperation()
@property (copy,nonatomic)NSURL * url;
@property (weak,nonatomic)UIImageView * imageview; //不参对应的UIImageview生命周期
@end
@implementation SycDownloadImageOperation
-(instancetype)initWithURL:(NSURL *)url ImageView:(UIImageView *)imageview{
    self = [super init];
    if (!self) {
        return nil;
    }
    _url = url;
    _imageview = imageview;
    return self;
}
-(void)main{
    if ([self isCancelled])
    {
        return;
    }
    NSData * data = [NSData dataWithContentsOfURL:self.url options:NSDataReadingUncached error:nil];
    if ([self isCancelled])
    {
        return;
    }
    UIImage *image = [[UIImage alloc] initWithData:data];
    //注意,这里一定要到主线程上更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        _imageview.image = image;
    });
}

@end

如何使用

SycDownloadImageOperation * operation = [[SycDownloadImageOperation alloc] initWithURL:imageURL ImageView:imageview];
[self.downloadQueue addOperation:operation];

异步的NSOperation

异步的NSOperation要自己维护队列的状态。就是isFinishedisExecuting两个属性。至少要实现的四个方法

isFinished
isExecuting
isAsynchronous
start

这里我们异步的用NSURLSession去下载,并且实时返回下载的进度。
头文件

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef void(^ProgressBlock)(CGFloat currentProgress);
typedef void(^FinishBlock)(BOOL successed,NSError * error,UIImage * image);

@interface DownloadImageOperation : NSOperation
-(instancetype)initWithURL:(NSURL*)url ProgressCallBack:(ProgressBlock)progressCallBack FinishedCallBack:(FinishBlock)finishedCallBack;
@end

.m文件

#import "DownloadImageOperation.h"
@interface DownloadImageOperation ()<NSURLSessionDelegate,NSURLSessionDownloadDelegate>{
    BOOL finished;
    BOOL executing;
}
@property (strong,nonatomic)NSURL * url;
@property (strong,nonatomic)NSURLSession * session;
@property (copy,nonatomic)ProgressBlock progressBlock;
@property (copy,nonatomic)FinishBlock finishBlock;
@property (strong,nonatomic)UIImage * image;
@property (strong,nonatomic)NSURL * cacheURL;
@end

@implementation DownloadImageOperation
-(instancetype)initWithURL:(NSURL*)url CacheURL:(NSURL *)cacheurl ProgressCallBack:(ProgressBlock)progressCallBack FinishedCallBack:(FinishBlock)finishedCallBack{
    self = [super init];
    if (!self) {
        return nil;
    }
    _url = url;
    _progressBlock = progressCallBack;
    _finishBlock = finishedCallBack;
    _cacheURL = cacheurl;
    finished = NO;
    executing = NO;
    return self;
}
-(instancetype)initWithURL:(NSURL *)url ProgressCallBack:(ProgressBlock)progressCallBack FinishedCallBack:(FinishBlock)finishedCallBack{
    return [self initWithURL:url CacheURL:nil ProgressCallBack:progressCallBack FinishedCallBack:finishedCallBack];
}

-(void)start{
    if ([self isCancelled])
    {
        // Must move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        finished = NO;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}
-(BOOL)isFinished{
    return finished;
}
-(BOOL)isExecuting{
    return executing;
}
-(BOOL)isAsynchronous{
    return YES;
}


-(void)main{
    self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue currentQueue]];
    NSURLSessionDownloadTask * task = [self.session downloadTaskWithURL:self.url];
    [task resume];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    BOOL successed = self.image == nil?NO:YES;
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        self.finishBlock(successed,error,self.image);
    });
    [self setOperationFinished];
    return;
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
    NSData * data = [NSData dataWithContentsOfURL:location];
    self.image = [UIImage imageWithData:data];
    if (self.cacheURL != nil) {
        NSFileManager * defaultManager = [NSFileManager defaultManager];
        NSError * error = nil;
        [defaultManager moveItemAtURL:location toURL:self.cacheURL error:&error];
        if (error) {
            NSLog(@"%@",error.localizedDescription);
        }
    }
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    if (self.isCancelled) {
        [self setOperationFinished];
    }
    CGFloat progress = (CGFloat)totalBytesWritten/(CGFloat)totalBytesExpectedToWrite;
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        self.progressBlock(progress);
    });
}
-(void)setOperationFinished{
    [self.session invalidateAndCancel];
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    executing = NO;
    finished = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];

}
@end

你可能感兴趣的:(多线程,ios,队列,并行,NSOperatio)