我想在完成或取消它之后再次启动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)
答案 0 :(得分:4)
让我概述一系列备选方案。第一个是解决你问题中的直接战术问题,而后两个是进一步改进,复杂性越来越高。
操作开始后,您无法呼叫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()
}
}
值得注意的是,连续加载图像会不必要地慢。您可以使用以下内容同时加载它们:
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()
}
}
即使这样也有问题。另一个问题是,当您“取消”时,它可能会继续尝试下载当前正在下载的资源,因为您没有使用可取消的网络请求。
更好的方法是将下载(通过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
}
}