2016-10-14 20 views
3

Note:虽然在SO中还存在其他类似的问题,但其中没有一个作者似乎自己控制Operation的生命周期。请在提及另一个问题之前仔细阅读。Alamofire请求不在NS内运行完成块

我在Swift 3.0中创建了一个[NS]操作来下载,解析和缓存核心数据中的一些数据。

起初,我在手术中使用了main()方法来执行手头的任务,它运行良好。现在我需要运行几个单独的任务来检索有关此步骤中获得的每个/每个设备的信息。为此,我需要确保设备实际上位于Core Data中,然后才能获取其他信息。出于这个原因,我想确保在发出相关请求之前,我确定何时完成任务 - 哪一个是所有设备在缓存中安全无恙 - 何时完成。

问题是,即使我已检查Alamofire确实执行了请求并且服务器确实发送了数据,但标记为注释[THIS WONT EXECUTE!]的完成块永远不会执行。这导致队列停顿,因为操作在所述完成块内被标记为finished,这是期望的行为。

有没有人有关于这里可能发生什么的任何想法?

class FetchDevices: Operation { 
var container: NSPersistentContainer! 
var alamofireManager: Alamofire.SessionManager! 
var host: String! 
var port: Int! 

private var _executing = false 
private var _finished = false 

override internal(set) var isExecuting: Bool { 
    get { 
     return _executing 
    } 

    set { 
     willChangeValue(forKey: "isExecuting") 
     _executing = newValue 
     didChangeValue(forKey: "isExecuting") 
    } 
} 

override internal(set) var isFinished: Bool { 
    get { 
     return _finished 
    } 

    set { 
     willChangeValue(forKey: "isFinished") 
     _finished = newValue 
     didChangeValue(forKey: "isFinished") 
    } 
} 

override var isAsynchronous: Bool { 
    return true 
} 

init(usingContainer container: NSPersistentContainer, usingHost host: String, usingPort port: Int) { 
    super.init() 

    self.container = container 
    self.host = host 
    self.port = port 

    let configuration = URLSessionConfiguration.default 
    configuration.timeoutIntervalForResource = 10 // in seconds 
    self.alamofireManager = Alamofire.SessionManager(configuration: configuration) 
} 

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

    self.isExecuting = true 

    alamofireManager!.request("http://apiurlfor.devices") 
     .validate() 
     .responseJSON { response in 
      // THIS WONT EXECUTE! 
      if self.isCancelled { 
       self.isExecuting = false 
       self.isFinished = true 
       return 
      } 

      switch response.result { 
      case .success(let value): 
       let jsonData = JSON(value) 

       self.container.performBackgroundTask { context in 
        for (_, rawDevice):(String, JSON) in jsonData { 
         let _ = Device(fromJSON: rawDevice, usingContext: context) 
        } 

        do { 
         try context.save() 
        } catch { 
         let saveError = error as NSError 
         print("\(saveError), \(saveError.userInfo)") 
        } 

        self.isExecuting = false 
        self.isFinished = true 
       } 

      case .failure(let error): 
       print("May Day! May Day! \(error)") 
       self.isExecuting = false 
       self.isFinished = true 
      } 
    } 
    } 
} 

一块可能有用的信息是,在我排队所有的操作的方法中,我使用的所有queue.waitUntilAllOperationsAreFinished()完成后执行完成处理程序。

回答

1

问题是您有其他东西阻塞主线程,responseJSON用于其关闭,导致死锁。您可以快速确认这一点,将responseJSON替换为.responseJSON(queue: .global())以使Alamofire使用主队列以外的队列,您将看到此行为更改。但是,如果您这样做(仅用于诊断目的),则应该将其更改回来,然后将注意力转到识别并消除阻塞主线程的任何内容(即不要在主线程上等待),因为您绝不应阻塞主线程。


您提到您致电waitUntilAllOperationsAreFinished。虽然这是等待一系列操作完成的令人陶醉的简单解决方案,但您绝对不应该在主线程中这样做。主线程不应该被阻塞(或者至少不会超过几毫秒)。它可能会导致一个不合标准的UX(应用程序冻结),并且您的应用程序容易被“看门狗”进程立即终止。我怀疑Alamofire的作者觉得如此舒适以便默认将他们的完成处理程序派发到主队列中的原因之一是,这不仅经常有用和方便,而且他们知道永远不会阻塞主线程。

当使用操作队列,典型的模式,以避免以往任何时候都等待是使用完成操作:

let completionOperation = BlockOperation { 
    // something that we'll do when all the operations are done 
} 

for object in arrayOfObjects { 
    let networkOperation = ... 
    completionOperation.addDependency(networkOperation) 
    queue.addOperation(networkOperation) 
} 

OperationQueue.main.addOperation(completionOperation) 

可以实现类似的,如果你使用的调度组,调度组“通知”的东西(虽然,通常,如果你使用操作队列,为了一致性的缘故,你通常会保持操作队列模式)。

如果你想打电话waitUntilAllOperationsAreFinished,在技术上你可以这样做,但是你应该这样做,如果你派遣“等待”到某个背景队列(例如一个全局队列,但显然不是你自己的操作队列上添加了所有这些操作)。但我认为这是一种浪费的模式(当你有一个完美的机制来指定一个完成操作时,为什么要等待一些全局工作线程等待操作完成)。

+0

感谢您花时间回答@Rob。我做了你的建议,没有改变。我会看看'阻塞主线程'的事情,但我不明白可能导致什么。你看到的代码是整个Operation.There没有更多的。 – reydelleon

+0

好@Rob,我认为你指出了正确的方向,但还有更多的工作要做。我在负责排队任务的方法中使用'queue.waitUntilAllOperationsAreFinished()'。如果我评论说,队列不会停顿,但实际上我需要等待所有操作完成,然后再调用完成处理程序。有没有办法解决这个问题? – reydelleon

+0

你的回答最终给我带来了正确的方向。我只是编辑它来添加一个链接到一个问题,解决了原始问题,我只有在分析问题后才发现,考虑到你的指针。 – reydelleon