美文网首页
面向对象的多任务管理Operation Queue

面向对象的多任务管理Operation Queue

作者: 玉米地里种玉米 | 来源:发表于2019-03-14 14:56 被阅读0次

    为了实现对并行任务的进一步控制, 除了GCD之外,iOS还提供了另外一个多线程机制,叫做:Operation Queue。它是基于GCD的面向对象封装,来了解一下它的用法。

    NSOperation和NSOperationQueue

    和GCD一样,我们可以把operation queue也认为是一个队列,这个队列在iOS中用NSOperationQueue表示,添加到operation queue中的任务除了会被并行执行外,还有它们自己的特点:

    • 它们并不遵从FIFO的原则;
    • 它们不再是简单的closure,而是被封装成了一个NSOperation类;

    但是NSOperation是一个抽象类,我们不能直接生成一个NSOperation对象,iOS有两个基于NSOperation实现了两个具象类:

    • NSBlockOperation - 我们可以把它理解为是一个closure的封装;
    • NSInvocationOperation - 在Objective-C中通过selector指定要调用的任务;

    但是,由于使用NSInvocationOperation可能带来的类型安全以及ARC安全的问题,Apple从iOS 8.1开始去掉了这个类。大家可以在iOS 8.1 API Diffs看到这个变更。因此,我们只使用NSBlockOperation就好了。

    通过Operation queue下载图片

    我们这次通过operation queue下载图片。首先,我们创建一个operation queue对象:

    class ViewController: UIViewController {
        // Omit for simplicity...
        @IBAction func downloadImages(sender: UIButton) {
            var queue = NSOperationQueue()
        }
        // Omit for simplicity...
    }
    

    然后,我们有两种不同的方式把任务添加到队列中:第一种是使用addOperationWithBlock直接向operation queue中添加一个closure,它会被自动转换成NSBlockOperation:

    class ViewController: UIViewController {
        // Omit for simplicity...
        @IBAction func downloadImages(sender: UIButton) {
            var queue = NSOperationQueue()
    
            queue.addOperationWithBlock({
                let img1 = 
                    Downloader.downloadImageWithURL(self.imageUrls[0])
                
                NSOperationQueue.mainQueue().addOperationWithBlock({
                    self.image1.image = img1
                    self.image1.clipsToBounds = true
                })
            })
        }
        // Omit for simplicity...
    }
    

    在上面这段代码中,有一点是需要注意的:更新UI的代码仍旧要放到app的主线程中完成。我们使用NSOperationQueue.mainQueue()得到app的主线程队列,然后使用addOperationWithBlock把更新UI的任务插入到主线程队列。

    或者,我们可以先手工创建一个NSBlockOperation对象,然后把它添加到operation queue。这样做的好处是,我们可以设置NSBlockOperation对象的completionBlock属性,在任务完成的时候得到通知,例如打印一个字符串:

    class ViewController: UIViewController {
        // Omit for simplicity...
        @IBAction func downloadImages(sender: UIButton) {
            var queue = NSOperationQueue()
    
            // 1. Create a NSBlockOperation object
            let op2 = NSBlockOperation(block: {
                let img2 = Downloader.downloadImageWithURL(self.imageUrls[1])
                
                NSOperationQueue.mainQueue().addOperationWithBlock({
                    self.image2.image = img2
                    self.image2.clipsToBounds = true
                })
            })
            
            // 2. Set finish callback
            op2.completionBlock = { print("image2 downloaded") }
    
            // 3. Add to operation queue manually
            queue.addOperation(op2)
        }
        // Omit for simplicity...
    }
    

    我们可以用类似的方法添加下载图片3和4的代码。然后Command + R编译执行,就可以看到图片在operation queue中并行下载了,打开控制台,我们还可以看到图片2/3/4下载完成的打印结果。

    设置任务之间的关联性

    在一开始我们就提到过,operation queue提供了更细粒度的任务控制,我们可以设置一个队列中不同任务的关联性,让它们先后执行。例如,我们要让图片按照432的顺序下载,同时让图片1保持并行下载,可以使用NSBlockOperation的addDependency方法:

    class ViewController: UIViewController {
        // Omit for simplicity...
        @IBAction func downloadImages(sender: UIButton) {
            var queue = NSOperationQueue()
    
            // Omit for simplicity...
            let op4 = NSBlockOperation(block: {
                let img4 = Downloader.downloadImageWithURL(self.imageUrls[3])
                
                NSOperationQueue.mainQueue().addOperationWithBlock({
                    self.image4.image = img4
                    self.image4.clipsToBounds = true
                })
            })
            
            op4.completionBlock = { print("image4 downloaded") }
    
            op3.addDependency(op4)
            op2.addDependency(op3)
    
            queue.addOperation(op4)
            queue.addOperation(op3)
            queue.addOperation(op2)
        }
    }
    

    这里我们把添加NSBlockOperation到operation queue的操作,放到了addDependency后面。确保它们在执行之前有正确的依赖关系。然后Command + R编译执行,就可以看到我们期望的结果了。

    取消任务的执行

    除了设置依赖关系,我们可以随时取消一个operation queue中所有的任务,但是取消的结果根据任务的状态会有所不同:

    • 所有已经完成的任务,取消操作不会有任何结果;
    • 如果一个任务被取消,所有和它关联的任务也会被取消;
    • 任务被取消之后,completionBlock仍旧会被执行;
      为了演示这个功能,我们先添加一个Cancel按钮:

    然后给它添加下面的事件处理方法:

    class ViewController: UIViewController {
        // Omit for simplicity...
        let queue = NSOperationQueue()
        
        @IBAction func cancelDownload(sender: AnyObject) {
            self.queue.cancelAllOperations()
        }
    }
    

    在上面的代码中,为了能在cancelDownload里访问我们创建的operation queue,我们要把之前创建的queue变成一个class property。

    然后Command + R编译执行,我们在第4个图片下载完之后点击“Cancel”,就会发现:

    由于图片1、3和4下载完成了,所以取消它没有任何效果;
    图片2的下载被取消了;
    如果我们按的快一些,可以赶在图片3的下载完成之前取消,那么图片2的下载也会被取消。为了获取任务的取消状态,我们可以访问NSBlockOperation对象的cancelled属性,如果任务取消成功,它的值为true,否则是false:

    class ViewController: UIViewController {
        // Omit for simplicity...
        let queue = NSOperationQueue()
        
        class ViewController: UIViewController {
        // Omit for simplicity...
        @IBAction func downloadImages(sender: UIButton) {
            let op3 = NSBlockOperation(block: {
                let img3 = Downloader.downloadImageWithURL(self.imageUrls[2])
                
                NSOperationQueue.mainQueue().addOperationWithBlock({
                    self.image3.image = img3
                    self.image3.clipsToBounds = true
                })
            })
            
            op3.completionBlock = { print("image3 is cancelled: \(op3.cancelled)") }
        }
    }
    

    然后Command + R重新编译执行,这次我们点击"Download"后快速点击“Cancel”,如果我们可以拦截到image3的下载,就会发现image2的下载同样会被取消,此时op3.cancelled的值就是true。

    相关文章

      网友评论

          本文标题:面向对象的多任务管理Operation Queue

          本文链接:https://www.haomeiwen.com/subject/pdnqmqtx.html