[NSBlockOperation addExecutionBlock:]:操作开始执行或完成后无法添加块

时间:2016-07-19 21:06:53

标签: ios swift swift2 nsoperation nsoperationqueue

我想在完成或取消它之后再次启动NSBlockOperation,但收到错误?任何人都知道哪里出错了?谢谢

let imageURLs = ["http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg",
    "http://adriatic-lines.com/wp-content/uploads/2015/04/canal-of-Venice.jpg",
    "http://algoos.com/wp-content/uploads/2015/08/ireland-02.jpg",
    "http://bdo.se/wp-content/uploads/2014/01/Stockholm1.jpg"]

class Downloader {

    class func downloadImageWithURL(url:String) -> UIImage! {

        let data = NSData(contentsOfURL: NSURL(string: url)!)
        return UIImage(data: data!)
    }
}

class ViewController: UIViewController {

    @IBOutlet weak var imageView1: UIImageView!
    var indeX = 0

    let operation1 = NSBlockOperation()
    var queue = NSOperationQueue()

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func didClickOnStart(sender: AnyObject) {
        queue = NSOperationQueue()

        operation1.addExecutionBlock { () -> Void in

            for _ in imageURLs {
                if !self.operation1.cancelled {
                    let img1 = Downloader.downloadImageWithURL(imageURLs[self.indeX])
                    NSOperationQueue.mainQueue().addOperationWithBlock({
                        self.imageView1.image = img1

                        print("indeX \(self.indeX)")
                        self.indeX++
                    })

                }
            }
        }
        queue.addOperation(operation1)
    }

    @IBAction func didClickOnCancel(sender: AnyObject) {
        self.queue.cancelAllOperations()
        print(operation1.finished)
    }
}  
  

输出

indeX 0
false
indeX 1
2016-07-20 02:00:26.157 ConcurrencyDemo[707:15846] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010c94be65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010e68bdeb objc_exception_throw + 48
    2   Foundation                          0x000000010cd369fe -[NSBlockOperation addExecutionBlock:] + 356
    3   ConcurrencyDemo                     0x000000010c766edd _TFC15ConcurrencyDemo14ViewController15didClickOnStartfS0_FPSs9AnyObject_T_ + 253
    4   ConcurrencyDemo                     0x000000010c767086 _TToFC15ConcurrencyDemo14ViewController15didClickOnStartfS0_FPSs9AnyObject_T_ + 54
    5   UIKit                               0x000000010d16a194 -[UIApplication sendAction:to:from:forEvent:] + 92
    6   UIKit                               0x000000010d56b7b7 -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 152
    7   UIKit                               0x000000010d16a194 -[UIApplication sendAction:to:from:forEvent:] + 92
    8   UIKit                               0x000000010d2d96fc -[UIControl sendAction:to:forEvent:] + 67
    9   UIKit                               0x000000010d2d99c8 -[UIControl _sendActionsForEvents:withEvent:] + 311
    10  UIKit                               0x000000010d2d9b43 -[UIControl _sendActionsForEvents:withEvent:] + 690
    11  UIKit                               0x000000010d2d8af8 -[UIControl touchesEnded:withEvent:] + 601
    12  UIKit                               0x000000010d1d949b -[UIWindow _sendTouchesForEvent:] + 835
    13  UIKit                               0x000000010d1da1d0 -[UIWindow sendEvent:] + 865
    14  UIKit                               0x000000010d188b66 -[UIApplication sendEvent:] + 263
    15  UIKit                               0x000000010d162d97 _UIApplicationHandleEventQueue + 6844
    16  CoreFoundation                      0x000000010c877a31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    17  CoreFoundation                      0x000000010c86d95c __CFRunLoopDoSources0 + 556
    18  CoreFoundation                      0x000000010c86ce13 __CFRunLoopRun + 867
    19  CoreFoundation                      0x000000010c86c828 CFRunLoopRunSpecific + 488
    20  GraphicsServices                    0x0000000110f5ead2 GSEventRunModal + 161
    21  UIKit                               0x000000010d168610 UIApplicationMain + 171
    22  ConcurrencyDemo                     0x000000010c76906d main + 109
    23  libdyld.dylib                       0x000000010f19492d start + 1
    24  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

1 个答案:

答案 0 :(得分:4)

让我概述一系列备选方案。第一个是解决你问题中的直接战术问题,而后两个是进一步改进,复杂性越来越高。

  1. 操作开始后,您无法呼叫addExecutionBlock。所以只需创建一个新的NSOperation

    例如:

    class ViewController: UIViewController {
    
        @IBOutlet weak var imageView1: UIImageView!
    
        weak var downloadOperation: NSOperation?    // make this weak
    
        var queue = NSOperationQueue()
    
        let imageURLs: [String] = ...
    
        @IBAction func didClickOnStart(sender: AnyObject) {
            downloadOperation?.cancel()             // you might want to stop the previous one if you restart this
    
            let operation = NSBlockOperation() {
                for (index, imageURL) in self.imageURLs.enumerate() {
                    guard let cancelled = self.downloadOperation?.cancelled where !cancelled else  { return }
    
                    let img1 = Downloader.downloadImageWithURL(imageURL)
                    NSOperationQueue.mainQueue().addOperationWithBlock() {
                        self.imageView1.image = img1
    
                        print("indeX \(index)")
                    }
                }
            }
            queue.addOperation(operation)
    
            downloadOperation = operation
        }
    
        @IBAction func didClickOnCancel(sender: AnyObject) {
            downloadOperation?.cancel()
        }
    
    }
    
  2. 值得注意的是,连续加载图像会不必要地慢。您可以使用以下内容同时加载它们:

    class ViewController: UIViewController {
    
        @IBOutlet weak var imageView1: UIImageView!
    
        var queue: NSOperationQueue = {
            let _queue = NSOperationQueue()
            _queue.maxConcurrentOperationCount = 4
            return _queue
        }()
    
        let imageURLs: [String] = ...
    
        @IBAction func didClickOnStart(sender: AnyObject) {
            queue.cancelAllOperations()
    
            let completionOperation = NSBlockOperation() {
                print("all done")
            }
    
            for (index, imageURL) in self.imageURLs.enumerate() {
                let operation = NSBlockOperation() {
                    let img1 = Downloader.downloadImageWithURL(imageURL)
                    NSOperationQueue.mainQueue().addOperationWithBlock() {
                        self.imageView1.image = img1
    
                        print("indeX \(index)")
                    }
                }
                completionOperation.addDependency(operation)
                queue.addOperation(operation)
            }
    
            NSOperationQueue.mainQueue().addOperation(completionOperation)
        }
    
        @IBAction func didClickOnCancel(sender: AnyObject) {
            queue.cancelAllOperations()
        }
    
    }
    
  3. 即使这样也有问题。另一个问题是,当您“取消”时,它可能会继续尝试下载当前正在下载的资源,因为您没有使用可取消的网络请求。

    更好的方法是将下载(通过NSURLSession执行)包装在自己的异步NSOperation子类中,并使其可取消,例如:

    class ViewController: UIViewController {
    
        @IBOutlet weak var imageView1: UIImageView!
    
        var queue: NSOperationQueue = {
            let _queue = NSOperationQueue()
            _queue.maxConcurrentOperationCount = 4
            return _queue
        }()
    
        let imageURLs: [String] = ...
    
        let session = NSURLSession.sharedSession()
    
        @IBAction func didClickOnStart(sender: AnyObject) {
            queue.cancelAllOperations()
    
            let completionOperation = NSBlockOperation() {
                print("all done")
            }
    
            for (index, imageURL) in self.imageURLs.enumerate() {
                let operation = ImageNetworkOperation(session: session, urlString: imageURL) { image, response, error in
                    self.imageView1.image = image
    
                    print("indeX \(index)")
                }
                completionOperation.addDependency(operation)
                queue.addOperation(operation)
            }
    
            NSOperationQueue.mainQueue().addOperation(completionOperation)
        }
    
        @IBAction func didClickOnCancel(sender: AnyObject) {
            queue.cancelAllOperations()
        }
    
    }
    

    其中

    /// Simple image network operation
    
    class ImageNetworkOperation : DataOperation {
    
        init(session: NSURLSession, urlString: String, imageCompletionHandler: (UIImage?, NSURLResponse?, NSError?) -> ()) {
            super.init(session: session, urlString: urlString) { data, response, error in
                var image: UIImage?
    
                if let data = data where error == nil {
                    image = UIImage(data: data)
                } else {
                    print(error)
                }
    
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    imageCompletionHandler(image, response, error)
                }
            }
        }
    
    }
    
    /// Simple network data operation
    ///
    /// This can be subclassed for image-specific operations, JSON-specific operations, etc.
    
    class DataOperation : AsynchronousOperation {
        var urlString: String
        var session: NSURLSession
        weak var downloadTask: NSURLSessionTask?
        var networkCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())?
    
        init(session: NSURLSession, urlString: String, networkCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) {
            self.session = session
            self.urlString = urlString
            self.networkCompletionHandler = networkCompletionHandler
    
            super.init()
        }
    
        override func main() {
            let task = session.dataTaskWithURL(NSURL(string: urlString)!) { data, response, error in
                self.networkCompletionHandler?(data, response, error)
                self.completeOperation()
            }
            task.resume()
            downloadTask = task
        }
    
        override func cancel() {
            super.cancel()
    
            downloadTask?.cancel()
        }
    
        override func completeOperation() {
            networkCompletionHandler = nil
    
            super.completeOperation()
        }
    }
    
    /// Asynchronous Operation base class
    ///
    /// This class performs all of the necessary KVN of `isFinished` and
    /// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
    /// a concurrent NSOperation subclass, you instead subclass this class which:
    ///
    /// - must override `main()` with the tasks that initiate the asynchronous task;
    ///
    /// - must call `completeOperation()` function when the asynchronous task is done;
    ///
    /// - optionally, periodically check `self.cancelled` status, performing any clean-up
    ///   necessary and then ensuring that `completeOperation()` is called; or
    ///   override `cancel` method, calling `super.cancel()` and then cleaning-up
    ///   and ensuring `completeOperation()` is called.
    
    public class AsynchronousOperation : NSOperation {
    
        override public var asynchronous: Bool { return true }
    
        private let stateLock = NSLock()
    
        private var _executing: Bool = false
        override private(set) public var executing: Bool {
            get {
                return stateLock.withCriticalScope { _executing }
            }
            set {
                willChangeValueForKey("isExecuting")
                stateLock.withCriticalScope { _executing = newValue }
                didChangeValueForKey("isExecuting")
            }
        }
    
        private var _finished: Bool = false
        override private(set) public var finished: Bool {
            get {
                return stateLock.withCriticalScope { _finished }
            }
            set {
                willChangeValueForKey("isFinished")
                stateLock.withCriticalScope { _finished = newValue }
                didChangeValueForKey("isFinished")
            }
        }
    
        /// Complete the operation
        ///
        /// This will result in the appropriate KVN of isFinished and isExecuting
    
        public func completeOperation() {
            if executing {
                executing = false
            }
    
            if !finished {
                finished = true
            }
        }
    
        override public func start() {
            if cancelled {
                finished = true
                return
            }
    
            executing = true
    
            main()
        }
    
        override public func main() {
            fatalError("subclasses must override `main`")
        }
    }
    
    extension NSLock {
    
        /// Perform closure within lock.
        ///
        /// An extension to `NSLock` to simplify executing critical code.
        ///
        /// - parameter block: The closure to be performed.
    
        func withCriticalScope<T>(@noescape block: Void -> T) -> T {
            lock()
            let value = block()
            unlock()
            return value
        }
    }