2

I'm wanting to know how to wait for an operation that is sending a request to finish uploading (and get response) before executing another bit of code. I've attempted to do this with NSOperations:

    let testOp = BlockOperation {
        var result = 0

        for i in 1...1000000000 {
            result += i
        }
    }

    let logOp = BlockOperation {
        self.debug.log(tag: "test", content: "testing")
    }

    logOp.completionBlock = {
        print("------- logOp completed")
    }

    logOp.addDependency(testOp)

    let sendOp = BlockOperation {
        self.debug.sendLog() //uploads log, using URLSession.shared.dataTask
    }

    sendOp.completionBlock = {
        print("------- sendOp completed")
    }

    sendOp.addDependency(logOp)

    let emptyOp = BlockOperation {
        self.debug.empty()
    }

    emptyOp.completionBlock = {
        print("------- emptyOp completed")
    }

    emptyOp.addDependency(sendOp)

    let queue = OperationQueue()
    queue.addOperations([testOp, logOp, sendOp, emptyOp], waitUntilFinished: false)

Output:

------- logOp completed
*** Sending debug log (coming from self.debug.sendLog())
------- sendOp completed
------- emptyOp completed
*** sendLog uploaded (coming from self.debug.sendLog())

Based on my code I expected the output to be:

------- logOp completed
*** Sending debug log
*** sendLog uploaded
------- sendOp completed
------- emptyOp completed

How do I make it so? Can I do this with NSOperations?

Additional detail about the debug.sendLog() function:

func sendLog() {
    ...
    var urlRequest = URLRequest(url: url!)
    urlRequest.httpMethod = "POST"
    ...

    let session = URLSession.shared
    let task = session.dataTask(with: urlRequest, completionHandler: {
        (data, response, error) in

        ...

        DispatchQueue.main.async {
            print("*** sendLog uploaded")

        }
    })

    task.resume()
}
toast
  • 1,860
  • 2
  • 26
  • 49
  • "Based on my code I expected the output to be ...". To be clear, that expectation is wrong. `print("*** sendLog uploaded")` is run asynchronously on the *main queue* whereas NSOperationQueue defaults to the *global concurrent default (priority) dispatch queue*. – makadev Oct 06 '16 at 15:23
  • I adjusted my code to remove the `DispatchQueue.main.async` block entirely. This does not appear to alter the outcome. I was able to make the request wait by using semaphores, but this feels hackish. I dont see how NSoperations is useful. Sure you can get the status, and cancel a whole bunch of operations easily, but if you structured your code well, you shouldn't even get to that far down the track that you need to cancel. – toast Oct 06 '16 at 23:59
  • 1
    True, another Problem is that dataTask is already asynchronously executed with `task.resume()`. You may need another concept which can handle the asynchronous "callback"/"future"/"promise" way or - for simplicity - [a synchronous request](http://stackoverflow.com/a/34308158/3828957) as you mentioned *or another mechanism*. The semaphore variant may look/feel hackish but it's a short and maintainable solution and it's ok as long as you keep an eye on not overusing it and not blocking the main queue. – makadev Oct 07 '16 at 08:43
  • This is a [nice and well commented StackOverflow Entry](http://stackoverflow.com/questions/21198404/nsurlsession-with-nsblockoperation-and-queues) about the topic, objective-c code thought. This should keep the NSURLSession tasks in line and make cancelation possible. – makadev Oct 07 '16 at 08:58
  • @makadev thanks for comments, and links. It was very helpful. – toast Oct 07 '16 at 09:12

1 Answers1

3

You could add the completion block straight to the sendLog method and ditch all the block operation stuff.

func sendLog(withCompletion completion: (() -> Void)) {
    ...
    var urlRequest = URLRequest(url: url!)
    urlRequest.httpMethod = "POST"
    ...

    let session = URLSession.shared
    let task = session.dataTask(with: urlRequest, completionHandler: {
        (data, response, error) in

        ...
        completion?()
    })

    task.resume()
}

Then call your sendLog method like this:

sendLog(withCompletion: {
   // Execute the code you want to happen on completion here.
})
Jacob King
  • 6,025
  • 4
  • 27
  • 45