多线程多线程iOS 多线程

iOS开发多线程基础讲解四(NSOperation)

2016-02-23  本文已影响281人  si1ence

本文中所有代码演示均有GitHub源码,点击下载

NSOperation 是iOS中比较高级的并发编程的操作!

NSOperation : 封装了 GCD 中的"任务"!

NSOperationQueue : 封装了 GCD 中的"队列"!

其他基本的抽象类

一. 基本演练

NSInvocationOperation

start

- (void)opDemo1 {
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"Invocation"];

    // start方法:直接开始,会在当前线程执行 @selector 方法
    [op start];
}

- (void)downloadImage:(id)obj {

    NSLog(@"%@ %@", [NSThread currentThread], obj);
}

添加到队列

- (void)opDemo2 {
    NSOperationQueue *q = [[NSOperationQueue alloc] init];

    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];

    [q addOperation:op];
}

添加多个操作

- (void)opDemo3 {
    NSOperationQueue *q = [[NSOperationQueue alloc] init];

    for (int i = 0; i < 10; ++i) {
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];

        [q addOperation:op];
    }
}

执行效果:会开启多条线程,而且不是顺序执行。与GCD中并发队列&异步执行效果一样!

结论,在 NSOperation 中:

NSBlockOperation

- (void)opDemo4 {
    NSOperationQueue *q = [[NSOperationQueue alloc] init];
    #warning 打印结果是在主线程中,说明直接在主线程执行,没有开辟新的线程
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];

    [q addOperation:op];
}

使用 block 来定义操作,所有的代码写在一起,更简单,便于维护!

更简单的,直接添加 Block

- (void)opDemo5 {
    NSOperationQueue *q = [[NSOperationQueue alloc] init];

    for (int i = 0; i < 10; ++i) {
        [q addOperationWithBlock:^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        }];
    }
}

向队列中添加不同的操作

- (void)opDemo5 {
    NSOperationQueue *q = [[NSOperationQueue alloc] init];

    for (int i = 0; i < 10; ++i) {
        [q addOperationWithBlock:^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        }];
    }

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block %@", [NSThread currentThread]);
    }];
    [q addOperation:op1];

    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
    [q addOperation:op2];
}

线程间通讯

- (void)opDemo6 {
    NSOperationQueue *q = [[NSOperationQueue alloc] init];

    [q addOperationWithBlock:^{
        NSLog(@"耗时操作 %@", [NSThread currentThread]);

        // 主线程更新 UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新 UI %@", [NSThread currentThread]);
        }];
    }];
}

二. 高级演练

全局队列

/// 全局操作队列,统一管理所有的异步操作
@property (nonatomic, strong) NSOperationQueue *queue;

- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
    }
    return _queue;
}

最大并发操作数

/// MARK: - 最大并发操作数
- (void)opDemo1 {

    // 设置同时并发操作数
    self.queue.maxConcurrentOperationCount = 2;

    NSLog(@"start");

    for (int i = 0; i < 10; ++i) {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"%@ %d", [NSThread currentThread], i);
        }];

        [self.queue addOperation:op];
    }
}

暂停 & 继续

/// MARK: - 暂停 & 继续
- (IBAction)pauseAndResume {

    if (self.queue.operationCount == 0) {
        NSLog(@"没有操作");
        return;
    }

    // 暂停或者继续
    self.queue.suspended = !self.queue.isSuspended;

    if (self.queue.isSuspended) {
        NSLog(@"暂停 %tu", self.queue.operationCount);
    } else {
        NSLog(@"继续 %tu", self.queue.operationCount);
    }
}

取消全部操作

/// MARK: - 取消所有操作
- (IBAction)cancelAll {
    if (self.queue.operationCount == 0) {
        NSLog(@"没有操作");
        return;
    }

    // 取消对列中的所有操作,同样不会影响到正在执行中的操作!
    [self.queue cancelAllOperations];

    NSLog(@"取消全部操作 %tu", self.queue.operationCount);
}

依赖关系

#warning 不要添加循环操作依赖,一定要在添加进操作队列之前设置操作依赖
- (void)dependency {

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"登录 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"付费 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载 %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"通知用户 %@", [NSThread currentThread]);
    }];

    [op2 addDependency:op1];
    [op3 addDependency:op2];
    [op4 addDependency:op3];
    // 注意不要循环依赖
//    [op1 addDependency:op4];

    [self.queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
    [[NSOperationQueue mainQueue] addOperation:op4];

    NSLog(@"come here");
}

追加操作

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 1. 实例化操作对象
    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"11111--->%@", [NSThread currentThread]);
    }];

    // 2. 往当前操作中追加操作一
    [blockOperation1 addExecutionBlock:^{
        NSLog(@"追加任务1--->%@", [NSThread currentThread]);
    }];

    // 2. 往当前操作中追加操作二
    [blockOperation1 addExecutionBlock:^{
        NSLog(@"追加任务2--->%@", [NSThread currentThread]);
    }];

    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"22222--->%@", [NSThread currentThread]);
    }];


    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"33333--->%@", [NSThread currentThread]);
    }];

    // 将操作添加到主队列中
    //    [[NSOperationQueue mainQueue] addOperation:blockOperation1];
    //    [[NSOperationQueue mainQueue] addOperation:blockOperation2];
    //    [[NSOperationQueue mainQueue] addOperation:blockOperation3];
    /*
     输出结果:

     11111---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
     追加任务2---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
     追加任务1---><NSThread: 0x7fed62f07ba0>{number = 4, name = (null)}
     22222---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
     33333---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
     */


    // 将操作添加到非主队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    [queue addOperation:blockOperation1];
    [queue addOperation:blockOperation2];
    [queue addOperation:blockOperation3];
    /*
     输出结果:

     22222---><NSThread: 0x7fba18f08a20>{number = 9, name = (null)}
     11111---><NSThread: 0x7fba18f03780>{number = 7, name = (null)}
     33333---><NSThread: 0x7fba18d99c20>{number = 10, name = (null)}
     追加任务1---><NSThread: 0x7fba18f08770>{number = 8, name = (null)}
     追加任务2---><NSThread: 0x7fba18c06220>{number = 11, name = (null)}
     */

}

三. 与 GCD 的对比

四. 自定义操作

准备工作

// 实例化自定义操作
DownloadImageOperation *op = [[DownloadImageOperation alloc] init];
// 将自定义操作添加到下载队列
[self.downloadQueue addOperation:op];

需求驱动开发

目标一:设置自定义操作的执行入口

对于自定义操作,只要重写了 main 方法,当队列调度操作执行时,会自动运行 main 方法

注意:为了能够及时释放内存,main方法内部一般建立一个自动释放池,但是苹果官方文档不要求写!

- (void)main {
    @autoreleasepool {

        NSLog(@"main--->%@", [NSThread currentThread]);

        NSString *filePath = @"http://ww1.sinaimg.cn/bmiddle/c260f7abjw1exxbbyckd6j20gn0m8wgh.jpg";

        UIImage *image = [self downLoadImageWithStr:filePath];

        // 回到主线程设置UI
        [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{

            NSLog(@"setupUI--->%@", [NSThread currentThread]);

            self.imgView.image = image;
        }]];
    }
}

// 抽取出下载图片的方法
- (UIImage *)downLoadImageWithStr:(NSString *)filePath {

    // 1. 转化为地址
    NSURL *url = [NSURL URLWithString:filePath];

    // 2. 转化为NSData 二进制类型数据(下载方法,耗时方法)
    NSData *data = [NSData dataWithContentsOfURL:url];

    // 3. 转化为图片类型
    UIImage *image = [UIImage imageWithData:data];

    NSLog(@"downLoadImage ---> %@", [NSThread currentThread]);

    return image;
}

目标二:给自定义参数传递参数

/** 承载图片的容器 */
@property (nonatomic, strong) UIImageView *imgView;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    NSLog(@"touchBegin--->%@", [NSThread currentThread]);

    // 1. 实例化自定义操作对象
    NSDownLoadOperation *operation = [[NSDownLoadOperation alloc] init];

    // 2. 告诉操作在哪里显示图片
    // 注意: 不能做以下改变 self.imageView = [[UIImageView alloc] init];
    operation.imgView = self.imgView;

    // 3.1 将自定义操作添加到下载队列,操作启动后会执行 main
     [self.queue addOperation:operation];

    // 3.2 直接开始
    // 这样的话,全部操作都在当前线程(主线程)中进行操
    //[operation start];

    NSLog(@"touchEnd--->%@", [NSThread currentThread]);
}

注意,main 方法被调用时,属性已经准备就绪

目标三:如何回调?

利用系统提供的 CompletionBlock 属性
// 设置完成回调
[operation setCompletionBlock:^{
    NSLog(@"完成 %@", [NSThread currentThread]);
}];

自己定义回调 Block,在操作结束后执行

/// 完成回调 Block
@property (nonatomic, copy) void (^finishedBlock)(UIImage *image);
// 设置自定义完成回调
[op setFinishedBlock:^(UIImage *image) {
    NSLog(@"finished %@ %@", [NSThread currentThread], image);
}];
// 判断自定义回调是否存在
if (self.finishedBlock != nil) {
    // 通常为了简化调用方的代码,异步操作结束后的回调,大多在主线程
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.finishedBlock(image对象);
    }];
}

目标四:简化操作创建

///  实例化下载图像操作
///
///  @param URLString 图像 URL 字符串
///  @param finished  完成回调 Block
///
///  @return 下载操作实例
+ (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *image))finished;
+ (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished {
    DownloadImageOperation *operation = [[DownloadImageOperation alloc] init];

    operation.URLString = URLString;
    operation.finishedBlock = finished;

    return operation;
}
// 使用类方法实例化下载操作
DownloadImageOperation *operation = [DownloadImageOperation downloadImageOperationWithURLString:@"http://www.baidu.com/img/bdlogo.png" finished:^(UIImage *image) {
    NSLog(@"%@", image);
}];

// 将自定义操作添加到下载队列,操作启动后会执行 main 方法
[self.downloadQueue addOperation:op];

目标五:取消操作

在关键节点添加 isCancelled 判断

for (int i = 0; i < 10; ++i) {
    NSString *urlString = [NSString stringWithFormat:@"http://www.xxx.com/%04d.png", i];

    DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:urlString finished:^(UIImage *image) {
        NSLog(@"===> %@", image);
    }];

    // 将自定义操作添加到下载队列,操作启动后会执行 main 方法
    [self.downloadQueue addOperation:op];
}
_downloadQueue.maxConcurrentOperationCount = 2;
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

    [self.downloadQueue cancelAllOperations];
}

cancelAllOperations 会向队列中的所有操作发送 Cancel 消息

- (void)main {
    NSLog(@"%s", __FUNCTION__);

    @autoreleasepool {

        NSLog(@"下载图像 %@", self.URLString);
        // 模拟延时
        [NSThread sleepForTimeInterval:1.0];

        if (self.isCancelled) {
            NSLog(@"1.--- 返回");
            return;
        }

        // 判断自定义回调是否存在
        if (self.finishedBlock != nil) {
            // 通常为了简化调用方的代码,异步操作结束后的回调,大多在主线程
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                self.finishedBlock(self.URLString);
            }];
        }
    }
}

注意:如果操作状态已经是 Cancel,则不会执行 main 函数

上一篇 下一篇

猜你喜欢

热点阅读