取消封装在 NSOperation 中的 Alamofire 请求会导致多个 KVO?

2024-04-08

我的Xcode版本:6.3.2
Alamofire 版本:1.2.2(通过 Cocoapods 安装)

为了设置maxConcurrentOperationCount限制并发操作数NSOperationQueue,我包裹我的阿拉莫菲尔 https://github.com/Alamofire/AlamofireNSOperation 中的下载请求就像罗布建议的那样 https://stackoverflow.com/a/27022598/1576281.

的基本子类NSOperation像这样:

class ConcurrentOperation : NSOperation {

    override var concurrent: Bool {
        return true
    }

    override var asynchronous: Bool {
        return true
    }

    private var _executing: Bool = false
    override var executing: Bool {
        get {
            return _executing
        }
        set {
            if (_executing != newValue) {
                self.willChangeValueForKey("isExecuting")
                _executing = newValue
                self.didChangeValueForKey("isExecuting")
            }
        }
    }

    private var _finished: Bool = false;
    override var finished: Bool {
        get {
            return _finished
        }
        set {
            if (_finished != newValue) {
                self.willChangeValueForKey("isFinished")
                _finished = newValue
                self.didChangeValueForKey("isFinished")
            }
        }
    }

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

    func completeOperation() {
        executing = false
        finished  = true
    }

    override func start() {
        if (cancelled) {
            finished = true
            return
        }

        executing = true

        main()
    }
}

我的子类包装了一个 Alamofire 下载请求,如下所示:

class DownloadImageOperation : ConcurrentOperation {
    let URLString: String
    let downloadImageCompletionHandler: (responseObject: AnyObject?, error: NSError?) -> ()
    weak var request: Alamofire.Request?

    init(URLString: String, downloadImageCompletionHandler: (responseObject: AnyObject?, error: NSError?) -> ()) {
        self.URLString = URLString
        self.downloadImageCompletionHandler = downloadImageCompletionHandler
        super.init()
    }

    override func main() {
        let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
        request = Alamofire.download(.GET, URLString, destination).response { (request, response, responseObject, error) in
            if self.cancelled {
                println("Alamofire.download cancelled while downlading. Not proceed.")
            } else {
                self.downloadImageCompletionHandler(responseObject: responseObject, error: error)
            }
            self.completeOperation()
        }
    }

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

它覆盖cancel()并尝试取消 Alamofire 请求NSOperation取消。

我使用 KVO 观察者来观察完成NSOperationQueue.

private var testAlamofireContext = 0

class TestAlamofireObserver: NSObject {
    var queue = NSOperationQueue()

    init(delegate: ImageDownloadDelegate) {
        super.init()
        queue.addObserver(self, forKeyPath: "operations", options: .New, context: &testAlamofireContext)
    }

    deinit {
        queue.removeObserver(self, forKeyPath: "operations", context: &testAlamofireContext)
    }

    override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject: AnyObject], context: UnsafeMutablePointer<Void>) {
        if context == &testAlamofireContext {
            if self.queue.operations.count == 0 {
                println("Image Download Complete queue. keyPath: \(keyPath); object: \(object); context: \(context)")
            }
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }
}

我开始了这样的下载列表:

func downloadImages() {
    let imgLinks = [
        "https://farm4.staticflickr.com/3925/18769503068_1fc09427ec_k.jpg",
        "https://farm1.staticflickr.com/338/18933828356_4f57420df7_k.jpg",
        "https://farm4.staticflickr.com/3776/18945113685_ccec89d67a_o.jpg",
        "https://farm1.staticflickr.com/366/18333992053_725f21166e_k.jpg",
        "https://farm4.staticflickr.com/3777/18962702032_086453ee7a_k.jpg",
        "https://farm1.staticflickr.com/373/18930501406_4753ac021a_k.jpg",
        "https://farm1.staticflickr.com/283/18772907409_56ffbe573b_k.jpg",
        "https://farm1.staticflickr.com/314/18940901785_b0564b1c9b_o.jpg",
        "https://farm1.staticflickr.com/502/18949263495_88d75d2d2f_k.jpg",
        "https://farm4.staticflickr.com/3912/18938184302_6e0ca9ad31_k.jpg",
        "https://farm1.staticflickr.com/356/18957923475_3dc9df7634_k.jpg",
        "https://farm1.staticflickr.com/378/18925014986_e87feca9c7_o.jpg",
        "https://farm1.staticflickr.com/461/18949863812_ddf700bd03_o.jpg",
        "https://farm1.staticflickr.com/303/18920711216_4684ff4295_k.jpg",
        "https://farm1.staticflickr.com/558/18935058546_fc10d10855_k.jpg",
        "https://farm1.staticflickr.com/384/18955290345_fb93d17828_o.jpg",
        "https://farm1.staticflickr.com/366/18333992053_725f21166e_k.jpg",
        "https://farm4.staticflickr.com/3777/18962702032_086453ee7a_k.jpg",
        "https://farm1.staticflickr.com/373/18930501406_4753ac021a_k.jpg",
        "https://farm1.staticflickr.com/283/18772907409_56ffbe573b_k.jpg",
        "https://farm1.staticflickr.com/314/18940901785_b0564b1c9b_o.jpg",
        "https://farm1.staticflickr.com/502/18949263495_88d75d2d2f_k.jpg",
        "https://farm4.staticflickr.com/3912/18938184302_6e0ca9ad31_k.jpg",
        "https://farm1.staticflickr.com/356/18957923475_3dc9df7634_k.jpg",
        "https://farm1.staticflickr.com/378/18925014986_e87feca9c7_o.jpg",
        "https://farm1.staticflickr.com/461/18949863812_ddf700bd03_o.jpg",
        "https://farm1.staticflickr.com/303/18920711216_4684ff4295_k.jpg",
        "https://farm1.staticflickr.com/558/18935058546_fc10d10855_k.jpg",
        "https://farm1.staticflickr.com/366/18333992053_725f21166e_k.jpg",
        "https://farm4.staticflickr.com/3777/18962702032_086453ee7a_k.jpg",
        "https://farm1.staticflickr.com/373/18930501406_4753ac021a_k.jpg",
        "https://farm1.staticflickr.com/283/18772907409_56ffbe573b_k.jpg",
        "https://farm1.staticflickr.com/314/18940901785_b0564b1c9b_o.jpg",
        "https://farm1.staticflickr.com/502/18949263495_88d75d2d2f_k.jpg",
        "https://farm4.staticflickr.com/3912/18938184302_6e0ca9ad31_k.jpg",
        "https://farm1.staticflickr.com/356/18957923475_3dc9df7634_k.jpg",
        "https://farm1.staticflickr.com/378/18925014986_e87feca9c7_o.jpg",
        "https://farm1.staticflickr.com/461/18949863812_ddf700bd03_o.jpg",
        "https://farm1.staticflickr.com/303/18920711216_4684ff4295_k.jpg",
        "https://farm1.staticflickr.com/558/18935058546_fc10d10855_k.jpg",
        "https://farm4.staticflickr.com/3777/18962702032_086453ee7a_k.jpg",
        "https://farm1.staticflickr.com/373/18930501406_4753ac021a_k.jpg",
        "https://farm1.staticflickr.com/283/18772907409_56ffbe573b_k.jpg",
        "https://farm1.staticflickr.com/314/18940901785_b0564b1c9b_o.jpg",
        "https://farm1.staticflickr.com/502/18949263495_88d75d2d2f_k.jpg",
        "https://farm4.staticflickr.com/3912/18938184302_6e0ca9ad31_k.jpg",
        "https://farm1.staticflickr.com/356/18957923475_3dc9df7634_k.jpg",
        "https://farm1.staticflickr.com/378/18925014986_e87feca9c7_o.jpg",
        "https://farm1.staticflickr.com/461/18949863812_ddf700bd03_o.jpg",
        "https://farm1.staticflickr.com/303/18920711216_4684ff4295_k.jpg",
        "https://farm1.staticflickr.com/558/18935058546_fc10d10855_k.jpg",
        "https://farm1.staticflickr.com/266/18956724112_6e61a743a5_k.jpg"
    ]

    var testAlamofireObserver = TestAlamofireObserver()
    testAlamofireObserver!.queue.maxConcurrentOperationCount = 5

    for imgLink in imgLinks {
        let operation = DownloadImageOperation(URLString: imgLink) {
            (responseObject, error) in

            if responseObject == nil {
                // handle error here
                println("failed: \(error)")
            } else {
                println("\(responseObject?.absoluteString) downloaded.")
            }
        }
        testAlamofireObserver!.queue.addOperation(operation)
    }
}

如果队列完成而没有收到任何取消,则日志输出应为:

2015-06-22 17:11:04.206 RSS Wallpaper Switchr[46250:714702] Optional(Optional("https://farm1.staticflickr.com/461/18949863812_ddf700bd03_o.jpg")) downloaded.
...
...
...
2015-06-22 17:11:56.979 RSS Wallpaper Switchr[46250:714702] Optional(Optional("https://farm1.staticflickr.com/461/18949863812_ddf700bd03_o.jpg")) downloaded.
2015-06-22 17:11:56.979 RSS Wallpaper Switchr[46250:714702] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x6180002354a0>{name = 'NSOperationQueue 0x6180002354a0'}; context: 0x000000010007eb70

如果队列收到cancelAllOperations(),日志输出应该是:

2015-06-22 17:16:29.691 RSS Wallpaper Switchr[46467:720630] Optional(Optional("https://farm1.staticflickr.com/366/18333992053_725f21166e_k.jpg")) downloaded.
2015-06-22 17:16:32.632 RSS Wallpaper Switchr[46467:720630] Alamofire.download cancelled while downlading. Not proceed.
...
...
2015-06-22 17:16:32.642 RSS Wallpaper Switchr[46467:720630] Alamofire.download cancelled while downlading. Not proceed.
2015-06-22 17:16:32.643 RSS Wallpaper Switchr[46467:720630] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x600000024c20>{name = 'NSOperationQueue 0x600000024c20'}; context: 0x000000010007eb70

然而,如果我改变了maxConcurrentOperationCount如上所述的非默认值,并且队列接收cancelAllOperations(),日志变成:

2015-06-22 17:17:56.427 RSS Wallpaper Switchr[46606:722523] Optional(Optional("https://farm4.staticflickr.com/3777/18962702032_086453ee7a_k.jpg")) downloaded.
2015-06-22 17:17:58.675 RSS Wallpaper Switchr[46606:722523] Alamofire.download cancelled while downlading. Not proceed.
...
...
2015-06-22 17:17:58.677 RSS Wallpaper Switchr[46606:722523] Alamofire.download cancelled while downlading. Not proceed.
2015-06-22 17:17:58.678 RSS Wallpaper Switchr[46606:722720] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x608000424ee0>{name = 'NSOperationQueue 0x608000424ee0'}; context: 0x000000010007eb70
2015-06-22 17:17:58.678 RSS Wallpaper Switchr[46606:722560] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x608000424ee0>{name = 'NSOperationQueue 0x608000424ee0'}; context: 0x000000010007eb70
2015-06-22 17:17:58.678 RSS Wallpaper Switchr[46606:722574] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x608000424ee0>{name = 'NSOperationQueue 0x608000424ee0'}; context: 0x000000010007eb70
2015-06-22 17:17:58.678 RSS Wallpaper Switchr[46606:722719] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x608000424ee0>{name = 'NSOperationQueue 0x608000424ee0'}; context: 0x000000010007eb70
2015-06-22 17:17:58.678 RSS Wallpaper Switchr[46606:722721] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x608000424ee0>{name = 'NSOperationQueue 0x608000424ee0'}; context: 0x000000010007eb70
2015-06-22 17:17:58.678 RSS Wallpaper Switchr[46606:722572] Image Download Complete queue. keyPath: operations; object: <NSOperationQueue: 0x608000424ee0>{name = 'NSOperationQueue 0x608000424ee0'}; context: 0x000000010007eb70

The KVO observeValueForKeyPath从多个不同的线程执行。线程的数量可以是可变的。这将导致KVO的完成函数被执行多次。如果我不更改默认值,这种情况就不会发生maxConcurrentOperationCount或不request?.cancel() for Alamofire.Request.

为什么我关心 KVO 完成函数的多次执行?我的目的是启动一个下载队列,当足够的下载完成时,取消剩余的操作,即使是未启动的或正在下载的操作,然后为下载做一些事情。完成函数应该只执行一次,两个因素 (1) 改变了默认值maxConcurrentOperationCount(2) 不request?.cancel() for Alamofire.Request可能与此有关。我想知道为什么以及如何纠正这个问题。


我不觉得你描述的多重 KVN 行为有那么令人惊讶。文档中没有任何内容表明,当取消所有操作时,单个 KVN 会operations将导致。事实上,人们可以安全地推断出您描述的行为应该是预期的(因为它不会抢先杀死所有这些工作线程,而是发送一个cancel向每个操作发送消息,每个操作负责在自己的时间内响应该消息;我没想到operations直到操作最终实际完成为止)。

就我个人而言,我建议完全废除这种观察者模式。您的代码不应取决于是否NSOperationQueue是否立即删除所有操作。相反,我建议您依靠现有的downloadImageCompletionHandler关闭,无论请求是否完成都会调用它。只需要关闭看看error反对弄清楚它是否被取消或是否因其他原因失败。


如果您的目的是知道所有这些操作何时完成,我不会依赖operationsKVN。相反,我可能会创建一个完成操作,具体取决于所有其他请求:

let completionOperation = NSBlockOperation() {                    // create completion operation
    // do whatever you want here
}

for imgLink in imgLinks {
    let operation = DownloadImageOperation(URLString: imgLink) { responseObject, error in
        if error != nil {
            if error!.code == NSURLErrorCancelled && error!.domain == NSURLErrorDomain {
                println("everything OK, just canceled")
            } else {
                println("error=\(error)")
            }
        }
        if responseObject != nil {
            println("\(responseObject?.absoluteString) downloaded.")
        }
    }

    completionOperation.addDependency(operation)                 // add dependency

    testAlamofireObserver!.queue.addOperation(operation)
}

NSOperationQueue.mainQueue().addOperation(completionOperation)   // schedule completion operation on some other queue (so that when I cancel everything on that other queue, I don't cancel this, too)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

取消封装在 NSOperation 中的 Alamofire 请求会导致多个 KVO? 的相关文章

  • 如何用Block简化回调逻辑?

    假设我需要与一个提供协议的类进行通信 并在操作完成时调用委托方法 如下所示 protocol SomeObjectDelegate required void stuffDone id anObject void stuffFailed e
  • 使用prepareForSegue传递数据

    我试图将数据从viewController 1传递到viewController2 我有2个按钮和1个segue 因此有一个segue标识符 这2个按钮 按下时每个按钮应显示 1个标签用于显示标题 1个textView用于显示定义 我很难显
  • 使用 Cocoa/OSX 合并/堆叠两个图像

    我有一个 CGImageRef 我们称之为原始图像 和一个透明 png 水印 我正在尝试编写一种方法将水印放置在原始内容之上 并返回 CGImageRef 在 iOS 中 我会使用 UIKit 将它们绘制到上下文中 但这在 OSX 中似乎不
  • 为每行 NSTableView 文本着色

    我有一个 NSTableView 它显示我拥有的对象数组 对于每个对象 行 我想根据在每个对象上运行的函数的结果来更改显示的文本的颜色 因此 例如表中存在于另一个列表 或其他一些要求 中的所有对象 我想以绿色文本显示它们 不存在的对象以红色
  • 如何在 Swift 中创建 UIAlertView?

    我一直在努力在 Swift 中创建 UIAlertView 但由于某种原因我无法得到正确的语句 因为我收到此错误 找不到接受提供的 init 重载 论点 我是这样写的 let button2Alert UIAlertView UIAlert
  • 如何组合两个 SwiftyJSON 对象

    我有一个 swiftyJSON 对象 例如 location http img http commentCount 0 timestamp 1432460217550 我希望能够向其附加另一个 swiftyJSON 对象 使其看起来像 lo
  • 针对 iOS 10.3 进行编译,但模块“SwiftUICharts”的最低部署目标为 iOS 13.0

    知道如何仅在 iOS 版本超过 iOS 13 时导入 SwiftUICharts 框架吗 我通过 文件 gt Swift 包 gt 添加包依赖项 添加了此框架 我的应用程序目标必须是 iOS 10 我将此框架导入到 swiftui 控制器中
  • 在 UITableViewController 中重新排序行后 UI 更新不正确

    因此 我对表中的行重新排序 用户界面最终结果不正确 场景如下 表内容原文 a b c d e 如果我移动第 0 行 当前a 到第 4 行 当前e 我看到的最终结果是 c d e a a 一些背景 该表正在读取 Realm 对象的列表 我确认
  • 带约束的嵌套集合视图的意外行为 (Swift 4)

    我的表格视图中有一个单元格 其中包含水平分页集合视图 该集合视图的每个页面内都有一个垂直集合视图 为了避免 滚动滚动 问题 我在垂直集合视图中禁用了垂直滚动 垂直集合视图的单元格计数不是静态的 可以是任意数字 因此 这会产生一个问题 集合视
  • 如何将CIFilter应用到UIView上?

    根据Apple docs 过滤属性CALayer不支持iOS 当我使用正在申请的应用程序之一时CIFilter to UIView即 Splice Funimate 和 Artisto 的视频编辑器 Videoshow FX 这意味着我们可
  • Swift:协议、结构、类

    我开始学习 Swift 语言 但在理解协议 结构和类方面遇到了困难 我来自 Android 方面的编程 所以我相信 Swift 协议基本上是 Java 接口 其中每一个的正确用例是什么 这些类比并不 完全 正确 但这就是我所理解的要点 是的
  • UILabel 中的文本未垂直居中

    我使用以下代码创建了一个标签 func setupValueLabel valueLabel numberOfLines 1 valueLabel font UIFont name Avenir Black size 50 valueLab
  • 在 UITableView 的部分标题文本下方添加一些边距

    我已经设计了标题文本的样式 func tableView tableView UITableView cellForRowAtIndexPath indexPath NSIndexPath gt UITableViewCell let ce
  • 具有动态警报正文的快速本地通知

    所以我可以创建一个像这样的本地通知 var localNotification UILocalNotification localNotification fireDate NSDate timeIntervalSinceNow 7 loc
  • UITableView 快速获取 titleForHeadersInSection

    我想在 UITableView 的部分中设置标题的标题 语法是什么swift设置该部分中标题的标题 func tableView tableView UITableView titleForHeaderInSection section I
  • 为 Swift 对象/属性设置观察者

    我一直在寻找一种在连接到 Mac 的显示器数量发生变化时触发方法的方法 我知道我可以获得 NSScreen screens count 的值 但我需要找到一种方法来在该值发生变化时创建通知或其他内容 或者指示所连接的显示器数量发生变化的其他
  • 如何右对齐 UILabel?

    Remark 实施 myLabel textAlignment right does not解决了我的问题 这不是我所要求的 我想要实现的是让标签对齐右对齐 为了更清楚地说明 这就是如何left对齐外观 就是这样justify对齐外观 if
  • 对成员“buildBlock()”的引用不明确

    我一直在尝试使用 Swift UI 为 iOS 13 制作一个应用程序 但我不断收到这个奇怪的错误 对成员 buildBlock 的引用不明确 无论我做什么 错误都不会消失 我尝试一次对代码段进行注释 以查看哪一部分可能导致了问题 但唯一有
  • 以编程方式从底部裁剪图像

    我正在开发自定义相机应用程序 一切进展顺利 但我在从底部裁剪图像时遇到了问题 即 裁剪后的图像与原始图像具有完全相同的宽度 但高度将为原始图像的 1 3 并且必须从底部开始 斯威夫特3解决方案 func cropBottomImage im
  • iOS 防止计时器 UILabel 在数字变化时“晃动”

    我有一个UILabel它以以下格式显示计时器的输出MM ss SS 分 秒 厘秒 但是随着厘秒宽度的变化 它从左向右 摇动 例如 11 比 33 窄 有什么办法可以减轻这种情况吗 我尝试过将其居中 给它固定的宽度 但它们似乎没有帮助 从iO

随机推荐