2016-07-19 23 views
3

Ich versuche, NSBlockOperation nach Abschluss oder Abbruch erneut zu starten, aber einen Fehler erhalten? Irgendjemand hat eine Idee, wo ist ein Fehler?[NSBlockOperation addExecutionBlock:]: Blöcke können nicht hinzugefügt werden, nachdem der Vorgang ausgeführt oder beendet

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

Ausgabe

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) 

Antwort

4

Lassen Sie mich eine Reihe von Alternativen skizzieren. Die erste behandelt nur das unmittelbare taktische Problem in Ihrer Frage, und die beiden letztgenannten sind weitere Verfeinerungen von zunehmender Komplexität.

  1. Sie können addExecutionBlock nicht aufrufen, sobald ein Vorgang gestartet wurde. Erstellen Sie einfach einen neuen NSOperation.

    Zum Beispiel:

    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. Es ist erwähnenswert, dass dies unnötig langsam sein wird, nacheinander die Bilder geladen werden. Sie könnten sie gleichzeitig mit etwas wie laden:

    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. Auch das hat Probleme. Das andere Problem besteht darin, dass bei einem "Abbrechen" möglicherweise weiterhin versucht wird, die aktuell heruntergeladene Ressource herunterzuladen, da Sie keine abbrechbare Netzwerkanforderung verwenden.

    Ein noch besserer Ansatz wäre, um den Download zu wickeln in seiner eigenen asynchronen NSOperation Unterklasse (die über NSURLSession durchgeführt werden soll), und es kündbare machen, zum Beispiel:

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

    Wo

    /// 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 
        } 
    } 
    
+1

sehr gut erklärt –

+1

Vielen Dank 'Rob' für die Hilfe. Du hast es sehr gut erklärt. Klappt wunderbar. – ZAFAR007

+0

Rob, kannst du bitte meine Frage überprüfen Ich habe ein kleines Problem in meinem Code mit dieser Methode. Ich denke, du kannst es reparieren. Danke 'http: // stackoverflow.com/fragen/38495531/append-images-in-array-in-sequence' – ZAFAR007