• iOS开发之多线程技术——NSOperation篇


    本篇将从四个方面对iOS开发中使用到的NSOperation技术进行讲解:


    一、什么是NSOperation

    二、我们为什么使用NSOperation

    三、在实际开发中如何使用NSOperation

      1、自定义NSOperation

      2、NSOperation的基本使用

      3、NSOperation实现线程间通信

        1)利用代理进行消息传递

        2)利用通知实现消息传递

        3)利用block进行消息传递

    四、与GCD比较


    一、什么是NSOperation

      NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。系统已经给我们封装了NSBlockOperation和NSInvocationOperation这两个实体类。使用起来也非常简单,不过我们更多的使用是自己继承并定制自己的操作。


    二、我们为什么使用NSOperation

      在iOS开发中,为了提升用户体验,我们通常会将操作耗时的操作放在主线程之外的线程进行处理。对于正常的简单操作,我们更多的是选择代码更少的GCD,让我们专注于自己的业务逻辑开发。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。 


    三、在实际开发中如何使用NSOperation

    1、自定义NSOperation

    在实际开发中,系统提供的NSOperation可能无法满足我们的需求,这时,我们就需要自定义我们自己的NSOperation

    @interface ViewController ()
    
    @property (nonatomic, weak) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        NSLog(@"touchesBegan------%@", [NSThread currentThread]);
        
        if (!self.imageView.image) {  // 避免重复下载,增强用户体验
            
            NSOperationQueue *queue = [[NSOperationQueue alloc] init];
            [queue setMaxConcurrentOperationCount:6];
            
            MyOperation *myO = [[MyOperation alloc] init];
            myO.imageView = self.imageView;
            [queue addOperation:myO];
            
        }
        
        NSLog(@"end");
    }
    
    // 自定义NSOperation
    @interface MyOperation : NSOperation
    
    @property (nonatomic, strong) UIImageView *imageView;
    
    @end
    
    // 实现自定义NSOperation
    @implementation MyOperation
    
    - (void)main {
        
        NSLog(@"%s----%@", __func__, [NSThread currentThread]);
        
        UIImage *image = [self downLoadImage:@"http://g.hiphotos.baidu.com/image/pic/item/f31fbe096b63f624cd2991e98344ebf81b4ca3e0.jpg"];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"%@======%@", image, [NSThread currentThread]);
            self.imageView.image = image;
        });
    }
    
    - (UIImage *)downLoadImage:(NSString *)urlString {
        NSLog(@"%s----%@",__func__, [NSThread currentThread]);
        
        NSURL *url = [NSURL URLWithString:urlString];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        
        NSLog(@"图片下载完成");
        
        return image;
    }
    
    @end

    2、NSOperation的基本使用

    #pragma mark
    #pragma mark - NSOperation高级操作1
    - (void)highLevelTest1 {
        /**
         NSOperation 相对于 GCD 来说,增加了以下管理线程的功能:
         1.NSOperation可以添加操作依赖:保证操作的执行顺序! --> 和GCD中将任务添加到一个串行队列中是一样的!一个串行队列会对应一条线程
         GCD 中的按顺序执行(串行队列) ---> 串行执行
         添加操作依赖之后,系统有可能串行执行保证任务的执行顺序,还有可能绿色线程同步技术,保证任务执行顺序
         */
        
        NSInvocationOperation *inO = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
        
        NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block1======%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block2======%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block3======%@", [NSThread currentThread]);
        }];
        
        /**
         四个操作都是耗时操作,并且要求按顺序执行,操作2是UI操作
         添加操作依赖的注意点
         1.一定要在将操作添加到操作队列中之前添加操作依赖
         2.不要添加循环依赖
         优点:对于不同操作队列中的操作,操作依赖依然有效
         */
        
        // 1.一定要在将操作添加到操作队列中之前添加操作依赖
        [block2 addDependency:block1];
        [block3 addDependency:block2];
        [inO addDependency:block3];
        // 2.不要添加循环依赖
        [block1 addDependency:block3];
        
        [[[NSOperationQueue alloc] init] addOperation:block1];
        [[[NSOperationQueue alloc] init] addOperation:block2];
        [[[NSOperationQueue alloc] init] addOperation:block3];
        
        [[NSOperationQueue mainQueue] addOperation:inO];
    }
    
    - (void)test {
        NSLog(@"测试%s-----%@", __func__, [NSThread currentThread]);
    }
    
    #pragma mark
    #pragma mark - NSOperation高级操作2
    - (void)highLevelTest2 {
        /**
         NSOperation高级操作
         应用场景:提高用户体验第一,当用户操作时,取消一切跟用户当前操作无关的进程,提升流畅度
         1.添加操作依赖
         2.管理操作:重点!是操作队列的方法
         2.1暂停/恢复 取消 操作
         2.2开启合适的线程数量!(最多不超过6条)
         
         一般开发的时候,会将操作队列设置成一个全局的变量(属性)
         */
        
        NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"---------");
        }];
        
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        [queue addOperationWithBlock:^{
            [self test];
        }];
        
        [queue addOperation:block1];
        
        // 1.暂停操作  开始滚动的时候
        [queue setSuspended:YES];
        
        // 2.恢复操作  滑动结束的时候
        [queue setSuspended:NO];
        
        // 3.取消所有操作  接收到内存警告
        [queue cancelAllOperations];
        
        // 3.1补充:取消单个操作调用该NSOperation的cancel方法
        [block1 cancel];
        
        // 4.设置线程最大并发数,开启合适的线程数量 实例化操作队列的时候
        [queue setMaxConcurrentOperationCount:6];
        
        /**
         遇到并发编程,什么时候选择 GCD, 什么时候选择NSOperation
         1.简单的开启线程/回到主线程,选择GCD:效率更高,简单
         2.需要管理操作(考虑到用户交互!)使用NSOperation
         */
    }
    
    #pragma mark
    #pragma mark - NSOperation简单操作
    - (void)BaseTest {
        // 1.实例化操作对象
        NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"blockOperation1---------%@", [NSThread currentThread]);
        }];
        
        // 往当前操作中追加操作
        [blockOperation1 addExecutionBlock:^{
            NSLog(@"addblockOperation1.1-----%@", [NSThread currentThread]);
        }];
        
        [blockOperation1 addExecutionBlock:^{
            NSLog(@"addblockOperation1.2-----%@", [NSThread currentThread]);
        }];
        
        /**
         当 NSBlockOperation中的任务数 > 1 之后,无论是将操作添加到主线程还是在主线程直接执行 start, NSBlockOperation中的任务执行顺序都不确定,执行线程也不确定!
         一般在开发的时候,要避免向 NSBlockOperation 中追加任务!
         如果任务都是在子线程中执行,并且不需要保证执行顺序!可以直接追加任务
         */
        
        NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"blockOperation2---------%@", [NSThread currentThread]);
        }];
        
        NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"blockOperation3---------%@", [NSThread currentThread]);
        }];
        
        // 将操作添加到非主队列中
        //    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //
        //    [queue addOperation:blockOperation1];
        //    [queue addOperation:blockOperation2];
        //    [queue addOperation:blockOperation3];
        
        // 将操作添加到主队列中
        [[NSOperationQueue mainQueue] addOperation:blockOperation1];
        [[NSOperationQueue mainQueue] addOperation:blockOperation2];
        [[NSOperationQueue mainQueue] addOperation:blockOperation3];
    }

    3、NSOperation实现线程间通信

    1)利用代理进行消息传递

    @interface ViewController ()<MyOperationDelegate>
    
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
        
        NSString *urlString = @"http://h.hiphotos.baidu.com/image/pic/item/30adcbef76094b366b2389d7a4cc7cd98d109d53.jpg";
        
        // 1.创建操作对象
        MyOperation *myO = [[MyOperation alloc] init];
        
        myO.delegate = self;
        
        // 3.告诉 myO 下载哪一张图片
        myO.urlString = urlString;
        
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue setMaxConcurrentOperationCount:6];
        
        // 4.将操作添加到操作队列中
        [queue addOperation:myO];
        
        NSLog(@"end");
    }
    
    #pragma mark
    #pragma mark - 实现操作的代理方法
    - (void)downImage:(UIImage *)image {
        NSLog(@"%s------%@", __func__, [NSThread currentThread]);
        self.imageView.image = image;
    }
    
    
    @protocol MyOperationDelegate <NSObject>
    
    - (void)downImage:(UIImage *)image;
    
    @end
    
    @interface MyOperation : NSOperation
    
    @property (nonatomic, copy) NSString *urlString;
    
    @property (nonatomic, weak) id<MyOperationDelegate> delegate;
    
    @end
    
    
    @implementation MyOperation
    
    #pragma mark
    #pragma mark - 重写NSOperation的main方法
    // 当把自定义的操作添加到操作队列中,或者直接调用操作的 start 方法后,都会自动来执行main 方法中的内容
    - (void)main {
        NSLog(@"%s------%@", __func__, [NSThread currentThread]);
        
        UIImage *image = [self downLoadImageSubThread];
        
        // 回到主线程执行代理方法
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.delegate respondsToSelector:@selector(downImage:)]) {
                [self.delegate downImage:image];
            }
        });
        
    }
    
    #pragma mark
    #pragma mark - 下载网络图片的方法
    - (UIImage *)downLoadImageSubThread {
        
        NSURL *url = [NSURL URLWithString:self.urlString];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        NSLog(@"下载完成");
        return [UIImage imageWithData:data];
        
    }
    
    @end

    2)利用通知实现消息传递

    @interface ViewController ()
    
    @property (nonatomic, weak) IBOutlet UIImageView *imageView04;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
        // 注册通知观察者
        // object:nil ,nil可以保证观察者接收任意类型的通知!
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setUpImageWithNotify:) name:@"FinishDownLoadImage" object:nil];
    }
    
    - (void)dealloc {
        // 通知用完之后需要移除
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
    // 接收到通知之后,执行的方法 noti:接收到的通知
    - (void)setUpImageWithNotify:(NSNotification *)noti {
        NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
        // 显示图片
        self.imageView04.image = noti.object;
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
        
        // 1.创建操作队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue setMaxConcurrentOperationCount:6];
        
        // 2.告诉操作下载哪张图片
        MyOperation *myO = [[MyOperation alloc] init];
        
        myO.urlString = @"http://h.hiphotos.baidu.com/image/pic/item/72f082025aafa40f7c884d31af64034f79f0198b.jpg";
        
        // 3.将操作添加到操作队列,会自动调用操作中的main 方法
        [queue addOperation:myO];
        
        NSLog(@"end");
    }
    
    /**************************************************/
    // 自定义NSOperation类
    @interface MyOperation : NSOperation
    
    @property (nonatomic, copy) NSString *urlString;
    
    @end
    
    // 实现自定义NSOperation类
    @implementation MyOperation
    
    - (void)main {
        NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
        
        // 在子线程下载好图片后再传给主线程
        UIImage *image = [self downLoadImage:self.urlString];
        
        // 图片下载完毕之后,利用通知告诉控制器,图片下载结束,并且将下载好的图片传递给控制器
        // 在主线程发送通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:@"FinishDownLoadImage" object:image];
        });
        
    }
    
    - (UIImage *)downLoadImage:(NSString *)urlString {
        
        NSURL *url = [NSURL URLWithString:urlString];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        return [UIImage imageWithData:data];
        
    }

    3)利用block进行消息传递

    @interface ViewController ()
    
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        NSLog(@"%s----%@",__func__, [NSThread currentThread]);
        
        NSString *urlString = @"http://h.hiphotos.baidu.com/image/pic/item/72f082025aafa40f7c884d31af64034f79f0198b.jpg";
        
        // 2.创建操作
        MyOperation *mo = [[MyOperation alloc] init];
        mo.urlString = urlString;
        
        mo.view = self.view;
        
        [mo downLoadWebImageWithBlock:^(UIImage *image) {
            self.imageView.image = image;
        }];
        
        [mo setCompletionBlock:^{
            NSLog(@"图片下载完成");
        }];
        
        NSLog(@"end");
        
    }
    
    /*********************************************************/
    // 自定义NSOperation类
    typedef void(^downLoadBlock)(UIImage *image);
    
    @interface MyOperation : NSOperation
    
    @property (nonatomic, strong) UIView *view;
    
    @property (nonatomic, copy) NSString *urlString;
    
    @property (nonatomic, copy) downLoadBlock block;
    
    - (void)downLoadWebImageWithBlock:(downLoadBlock)blk;
    
    @end
    
    
    // 实现自定义NSOperation类
    @implementation MyOperation
    
    #pragma mark
    #pragma mark - 实现block方法,方便直接回车调用
    - (void)downLoadWebImageWithBlock:(downLoadBlock)blk {
        if (blk) {
            self.block = blk;
            
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                UIImage *image = [self downLoadImage:self.urlString];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    
                    if (self.block) {
                        self.block(image);
                    }
                    
                });
            });
        }
    
    }
    
    #pragma mark
    #pragma mark - 下载图片的方法
    - (UIImage *)downLoadImage:(NSString *)strUrl {
        
        NSLog(@"%s----%@",__func__, [NSThread currentThread]);
        
        NSURL *url = [NSURL URLWithString:strUrl];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        NSLog(@"下载完成");
        
        return [UIImage imageWithData:data];
        
    }
    
    @end

    四、与GCD比较

    GCD:

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

    NSOperation:

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

  • 相关阅读:
    Cooperate with Myself
    A brief introduction of myself
    计算1+11+111+1111+........
    Jav实现F(n)=F(n-1)+F(n-2)+.....+F(1)+1
    查找二维数组中是否有符合的目标值
    排序算法
    时间复杂度
    Java代码实现单例模式
    查找一个字符串中重复出现字符的个数
    null,“”,empty的区别
  • 原文地址:https://www.cnblogs.com/Jepson1218/p/5180915.html
Copyright © 2020-2023  润新知