swift2 [NSBlock操作添加执行块:]:在操作开始执行或完成后不能添加块

8wigbo56  于 2022-11-06  发布在  Swift
关注(0)|答案(1)|浏览(207)

我正在尝试重新开始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)
yshpjwxd

yshpjwxd1#

让我概述一系列的备选方案。第一个方案只是解决你问题中的直接战术问题,后两个方案是进一步的改进,越来越复杂。
1.一旦操作开始,就不能调用addExecutionBlock。因此,只需创建一个新的Operation
例如:

class ViewController: UIViewController {

    @IBOutlet weak var imageView1: UIImageView!

    weak var downloadOperation: Operation?    // make this weak

    var queue = OperationQueue()

    let imageURLs: [String] = ...

    @IBAction func didClickOnStart(_ sender: Any) {
        downloadOperation?.cancel()             // you might want to stop the previous one if you restart this

        let operation = BlockOperation {
            for (index, imageURL) in self.imageURLs.enumerate() {
                guard let cancelled = self.downloadOperation?.cancelled where !cancelled else  { return }

                let img1 = Downloader.downloadImageWithURL(imageURL)
                OperationQueue.main.addOperation {
                    self.imageView1.image = img1

                    print("index \(index)")
                }
            }
        }
        queue.addOperation(operation)

        downloadOperation = operation
    }

    @IBAction func didClickOnCancel(_ sender: Any) {
        downloadOperation?.cancel()
    }

}

1.值得注意的是,这将是不必要的缓慢,连续加载图像。您可以同时加载它们与如下:

class ViewController: UIViewController {

    @IBOutlet weak var imageView1: UIImageView!

    var queue: OperationQueue = {
        let _queue = OperationQueue()
        _queue.maxConcurrentOperationCount = 4
        return _queue
    }()

    let imageURLs: [String] = ...

    @IBAction func didClickOnStart(_ sender: Any) {
        queue.cancelAllOperations()

        let completionOperation = BlockOperation {
            print("all done")
        }

        for (index, imageURL) in self.imageURLs.enumerate() {
            let operation = BlockOperation {
                let img1 = Downloader.downloadImageWithURL(imageURL)
                OperationQueue.main.addOperation {
                    self.imageView1.image = img1

                    print("index \(index)")
                }
            }
            completionOperation.addDependency(operation)
            queue.addOperation(operation)
        }

        OperationQueue.main.addOperation(completionOperation)
    }

    @IBAction func didClickOnCancel(_ sender: Any) {
        queue.cancelAllOperations()
    }
}

1.即使这样也有问题。另一个问题是,当你“取消”时,它很可能会继续尝试下载当前正在下载的资源,因为你没有使用可取消的网络请求。
一种更好的方法是将下载(将通过URLSession执行) Package 在其自己的异步Operation子类中,并使其可取消,例如:

class ViewController: UIViewController {
    var queue: OperationQueue = {
        let _queue = OperationQueue()
        _queue.maxConcurrentOperationCount = 4
        return _queue
    }()

    let imageURLs: [URL] = ...

    @IBAction func didClickOnStart(_ sender: Any) {
        queue.cancelAllOperations()

        let completion = BlockOperation {
            print("done")
        }

        for url in imageURLs {
            let operation = ImageDownloadOperation(url: url) { result in
                switch result {
                case .failure(let error): 
                    print(url.lastPathComponent, error)

                case .success(let image): 
                    OperationQueue.main.addOperation {
                        self.imageView1.image = img1

                        print("index \(index)")
                    }
                }
            }
            completion.addDependency(operation)
            queue.addOperation(operation)
        }

        OperationQueue.main.addOperation(completion)
    }

    @IBAction func didClickOnCancel(_ sender: AnyObject) {
        queue.cancelAllOperations()
    }
}

在哪里

/// Simple image network operation

class ImageDownloadOperation: DataOperation {
    init(url: URL, session: URLSession = .shared, networkCompletionHandler: @escaping (Result<UIImage, Error>) -> Void) {
        super.init(url: url, session: session) { result in
            switch result {
            case .failure(let error):
                networkCompletionHandler(.failure(error))

            case .success(let data):
                guard let image = UIImage(data: data) else {
                    networkCompletionHandler(.failure(DownloadError.notImage))
                    return
                }

                networkCompletionHandler(.success(image))
            }
        }
    }
}

/// Simple network data operation
///
/// This can be subclassed for image-specific operations, JSON-specific operations, etc.

class DataOperation: AsynchronousOperation {
    var downloadTask: URLSessionTask?

    init(url: URL, session: URLSession = .shared, networkCompletionHandler: @escaping (Result<Data, Error>) -> Void) {
        super.init()

        downloadTask = session.dataTask(with: url) { data, response, error in
            defer { self.complete() }

            guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
                networkCompletionHandler(.failure(error!))
                return
            }

            guard 200..<300 ~= response.statusCode else {
                networkCompletionHandler(.failure(DownloadError.invalidStatusCode(response)))
                return
            }

            networkCompletionHandler(.success(data))
        }
    }

    override func main() {
        downloadTask?.resume()
    }

    override func cancel() {
        super.cancel()

        downloadTask?.cancel()
    }
}

/// 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: Operation {

    override public var isAsynchronous: Bool { return true }

    private let stateLock = NSLock()

    private var _executing: Bool = false
    override private(set) public var isExecuting: Bool {
        get {
            stateLock.withCriticalScope { _executing }
        }
        set {
            willChangeValue(forKey: "isExecuting")
            stateLock.withCriticalScope { _executing = newValue }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _finished: Bool = false
    override private(set) public var isFinished: Bool {
        get {
            stateLock.withCriticalScope { _finished }
        }
        set {
            willChangeValue(forKey: "isFinished")
            stateLock.withCriticalScope { _finished = newValue }
            didChangeValue(forKey: "isFinished")
        }
    }

    /// Complete the operation
    ///
    /// This will result in the appropriate KVN of isFinished and isExecuting

    public func complete() {
        if isExecuting {
            isExecuting = false
        }

        if !isFinished {
            isFinished = true
        }
    }

    override public func start() {
        if isCancelled {
            isFinished = true
            return
        }

        isExecuting = 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>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

相关问题