• NSOperation类


    NSOperation 抽象类

    • NSOperation 是一个”抽象类”,不能直接使用
    • 抽象类的用处是定义子类共有的属性和方法
    • 在苹果的头文件中,有些抽象类和子类的定义是在同一个头文件中的
    • 子类:
      • NSInvocationOperation (调用)
      • NSBlockOperation (块)
    • NSOperationQueue 队列

    已经学习过的抽象类

    • UIGestureRecognizer
    • CAAnimation
    • CAPropertyAnimation

    基本演练

    NSInvocationOperation

    start

    • start 方法 会在当前线程执行 @selector 方法
    - (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);
    }

    添加到队列

    • 将操作添加到队列,会”异步”执行 selector 方法
    - (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];
    
        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];
    }
    • 可以向 NSOperationQueue 中添加任意 NSOperation 的子类

    线程间通讯

    - (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);
    }
    • 取消队列中所有的操作
    • 不会取消正在执行中的操作
    • 不会影响队列的挂起状态

    依赖关系

    /// MARK: - 依赖关系
    - (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");
    }

    与 GCD 的对比

    • GCD

      • 任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
      • GCD是底层的C语言构成的API
      • iOS 4.0 推出的,针对多核处理器的并发技术
      • 在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
      • 要停止已经加入 queueblock 需要写复杂的代码
      • 需要通过 Barrier 或者同步任务设置任务之间的依赖关系
      • 只能设置队列的优先级
      • 高级功能:
        • 一次性 once
        • 延迟操作 after
        • 调度组
    • NSOperation

      • 核心概念:把操作(异步)添加到队列(全局的并发队列)
      • OC 框架,更加面向对象,是对 GCD 的封装
      • iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层全部重写
      • Operation作为一个对象,为我们提供了更多的选择
      • 可以随时取消已经设定要准备执行的任务,已经执行的除外
      • 可以跨队列设置操作的依赖关系
      • 可以设置队列中每一个操作的优先级
      • 高级功能:
        • 最大操作并发数(GCD不好做)
        • 继续/暂停/全部取消
        • 跨队列设置操作的依赖关系

    自定义操作

    准备工作

    • 自定义 DownloadImageOperation 继承自 NSOperation
    • 代码调用
    // 实例化自定义操作
    DownloadImageOperation *op = [[DownloadImageOperation alloc] init];
    // 将自定义操作添加到下载队列
    [self.downloadQueue addOperation:op];

    需求驱动开发

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

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

    注意main 方法中需要使用自动释放池!

    - (void)main {
        @autoreleasepool {
            NSLog(@"%@", [NSThread currentThread]);
        }
    }

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

    • 定义属性
    /// 要下载图像的 URL 字符串
    @property (nonatomic, copy) NSString *URLString;
    • 代码调用
    // 实例化自定义操作
    DownloadImageOperation *op = [[DownloadImageOperation alloc] init];
    // 设置操作属性
    op.URLString = @"https://www.baidu.com/img/bdlogo.png";
    
    // 将自定义操作添加到下载队列,操作启动后会执行 main 方法
    [self.downloadQueue addOperation:op];

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

    目标三:如何回调?

    利用系统提供的 CompletionBlock 属性

    // 设置完成回调
    [op setCompletionBlock:^{
        NSLog(@"完成 %@", [NSThread currentThread]);
    }];
    • 只要设置了 CompletionBlock,当操作执行完毕后,就会被自动调用
    • CompletionBlock 既不在主线程也不在操作执行所在线程
    • CompletionBlock 无法传递参数

    自己定义回调 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(@"hello");
        }];
    }

    目标四:简化操作创建

    • 定义方法
    ///  实例化下载图像操作
    ///
    ///  @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 *op = [[DownloadImageOperation alloc] init];
    
        op.URLString = URLString;
        op.finishedBlock = finished;
    
        return op;
    }
    • 方法调用
    // 使用类方法实例化下载操作
    DownloadImageOperation *op = [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 消息

    • 调整 main 方法,在关键节点判断
    - (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);
                }];
            }
        }
    }
    
    - (void)start {
        [super start];
    
        NSLog(@"%s", __FUNCTION__);
    }

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

    • 队列调度操作时,首先执行 start 方法将线程放入可调度线程池
    • 操作执行时的入口是 main 方法
  • 相关阅读:
    理解爬虫原理
    中文词频统计与词云生成
    复合数据类型,英文词频统计
    字符串操作、文件操作,英文词频统计预处理
    了解大数据的特点、来源与数据呈现方式
    为Bootstrap模态对话框添加拖拽移动功能
    前端进阶学习笔记
    前端基础学习笔记
    MySQL学习笔记(模块二)
    MySQL学习笔记(模块一)
  • 原文地址:https://www.cnblogs.com/jiahao89/p/5118271.html
Copyright © 2020-2023  润新知