reydelleon reydelleon - 1 month ago 33
iOS Question

Alamofire request not running completion block inside NSOperation

Note
: While there are other, similar questions to this one in SO, in none of them the authors seem to be controlling the lifecycle of the
Operation
by themselves. Please, read through before referring to another question.

I created an [NS]Operation in Swift 3.0 to download, parse and cache some data in Core Data.

At first, I used the
main()
method in the Operation to execute the task at hand and it worked well. Now I need to run several separate tasks to retrieve information about each/every device I got in this step. For this, I need to ensure that the devices are actually in Core Data before I attempt to get the other information. For that reason, I want to make sure that I decide when the task is complete -- which is when ALL the devices are safe and sound in the cache -- before firing the dependent requests.

The problem is that even though I have checked that Alamofire does execute the request and the server does send the data, the completion block marked with the comment [
THIS WONT EXECUTE!
] is never executed. This causes the Queue to stall, since the Operation is marked as
finished
inside the said completion block, which is the desired behavior.

Does anyone has any ideas about what might be going on here?

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


A piece of information that may be useful is that in the method where I queue all the operations, I use
queue.waitUntilAllOperationsAreFinished()
to execute a completion handler after all is done.

Rob Rob
Answer

The problem is likely that you have something else that is blocking the main thread, which responseJSON uses for its closure. You can confirm this quickly replacing responseJSON with .responseJSON(queue: .global()) and see if the behavior changes. If so, you should change it back and then turn your attention to identify and eliminate whatever is blocking the main thread (e.g. maybe you're waiting for the operation?), as you should never block the main thread.