原创blog,转载请注明出处
blog.csdn.net/hello_hwc
欢迎关注我的iOS SDK详解专栏,这里有很多基础的文章
http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html
前言:在iOS开发 中,多线程是一个很重要的一个方面。iOS的多线程使用可以分为几个方面。由底层到上层分别是
这里不得不提到一点,操作系统是以线程为任务调度基本单位,对于一个单核CPU,实际上是采用时间片轮转来实现多线程的。而对于多核CPU,则可以实现真正的并发执行。
我的iOS SDK讲解系列并不会详细讲解pthread 和 NSThread,二者很少会用到,也很容易在使用时候出问题。
本文会简单讲解下NSOperationQueue以及NSOperation,并且给出一个简单的Demo实现串行和并行的NSOperation
pthread 是Unix层次的线程,相当底层。可以参见WIKI。
NSThread是对pthread的进一步封装,但是仍然在使用的时候出现各种各样的问题。
在iOS开发的过程中,有一点要谨记:
尽可能的使用上层API去实现,除非上层API没办法实现再考虑底层API
NSOperationQueue把线程操作抽象成队列操作,它是GCD的上层封装,队列中是一个个的NSOperation,NSOperation用来定义任务的具体执行。
使用NSOperationQueue和NSOperation相关的API可以方便而安全的的进行
- 取消一个任务
- 添加任务的依赖关系,例如任务3要再任务1和任务2执行完毕才能执行
- 设置一个Queue最多同时执行的任务数量。默认情况Queue会做到最优化执行。
- KVO监听任务的状态变化
另外,NSOperationQueue也分为两种
当把一个NSOperation添加到自定义Queue后,会在当前线程之外的另一个线程来执行操作。
写了个简单的Demo,异步下载3张图片。
- 为下载提供独立的进度条
- 每次队列只允许一个下载任务进行
下载链接
NSOperation用来封装要实现的任务,注意这些任务是一次性的,不能重复利用。通常执行任务要把Operation添加到OperationQueue。等到任务执行完毕后,Operation会被从Queue上移除并且销毁。
有几点要提到:
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
注意,这里的同步异步是相对任务本身的,同步的任务指的是start-main-结束,main结束后,则任务结束。异步的任务例如有网络请求的,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要自己维护队列的状态。就是isFinished
和isExecuting
两个属性。至少要实现的四个方法
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