ios - 重置 NSOperation

标签 ios swift nsoperation

我的网络请求需要以 FIFO 串行方式发生。如果由于网络问题(即离线、超时)而失败,我需要失败的请求在继续队列之前重试。我怎样才能做到这一点?

当操作失败时,我将执行和完成都设置为 - 假设这将作为 NSOperation 的“重置”。但是队列永远不会恢复。

有什么我想念的吗?

最佳答案

您可以构建一个执行重试的网络操作,并且仅在调用网络完成 block 时设置 isFinished,并确定不需要重试:

class NetworkOperationWithRetry: AsynchronousOperation {
    var session: NSURLSession
    var request: NSURLRequest
    var task: NSURLSessionTask?
    var networkCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())?

    init(session: NSURLSession = NSURLSession.sharedSession(), request: NSURLRequest, networkCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) {
        self.session = session
        self.request = request
        self.networkCompletionHandler = networkCompletionHandler
    }

    override func main() {
        attemptRequest()
    }

    func attemptRequest() {
        print("attempting \(request.URL!.lastPathComponent)")

        task = session.dataTaskWithRequest(request) { data, response, error in
            if error?.domain == NSURLErrorDomain && error?.code == NSURLErrorNotConnectedToInternet {
                print("will retry \(self.request.URL!.lastPathComponent)")
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * Int64(NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
                    self.attemptRequest()
                }
                return
            }

            print("finished \(self.request.URL!.lastPathComponent)")

            self.networkCompletionHandler?(data, response, error)
            self.networkCompletionHandler = nil
            self.completeOperation()
        }
        task?.resume()
    }

    override func cancel() {
        task?.cancel()
        super.cancel()
    }
}

你可以这样调用它:

let queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1     // I wouldn't generally do this, but just to illustrate that it's honoring operation queue dependencies/concurrency settings

let requests = urlStrings.map { NSURLRequest(URL: NSURL(string: $0)!) }
requests.forEach { request in
    queue.addOperation(NetworkOperationWithRetry(request: request) { data, response, error in
        // do something with the `data`, `response`, and `error`
    })
}

现在,这只是在 NSURLErrorNotConnectedToInternet 上重试,但您可以根据需要更改该逻辑。同样,我可能倾向于添加一些“最大重试”逻辑。此外,如果缺少 Internet 连接确实是问题所在,而不是在实现 Internet 连接之前重试,我可能倾向于将操作的 isReady 通知设置为可达性。

顺便说一句,上面使用了下面的AsynchronousOperation:

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

关于ios - 重置 NSOperation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38168506/

相关文章:

ios - 使用 NSURLConnection 运行 NSOperation 的多个实例?

ios - SwiftUI,在 HStack 内的屏幕外渲染文本

ios - iOS 应用上 Web Trends SDK 的谷歌移动广告问题

swift - 如何为 NSPersistentContainer 和 NSPersistentCloudKitContainer 创建同名但根据版本设置的变量?

ios - 核心数据 fatal error : unexpectedly found nil while unwrapping an Optional value

ios - 在应用程序吃午饭时初始化单例类

iphone - 将视网膜图像动态加载到标签栏

ios - 当我们开始旋转设备时和完成后将调用什么方法

android - 在 ios 和 android 上查看 localStorage

iphone - setDelegate Queue 在 iOS5 上不起作用,但在 iOS 6 上运行良好