Alamofire never calls encodingCompletion for upload with MultipartFormData when main thread is waiting for it to run

风流意气都作罢 提交于 2019-12-25 06:57:35

问题


I have code of this form:

func myFunction(<...>, completionHandler: (ResponseType) -> Void) {
    <prepare parameters>

    mySessionManager.upload(multipartFormData: someClosure,
        to: saveUrl, method: .post, headers: headers) { encodingResult in
          // encodingCompletion
          switch encodingResult {
                case .failure(let err):
                    completionHandler(.error(err))
                case .success(let request, _, _):
                    request.response(queue: self.asyncQueue) { response in
                        // upload completion
                        <extract result>
                        completionHandler(.success(result))
                    }
            }
     }
}

And testing code like this:

func testMyFunction() {
    <prepare parameters>

    var error: Error? = nil
    var result: MyResultType? = nil

    let sem = DispatchSemaphore(value: 0)
    var ran = false
    myFunction(<...>) { response in
        if ran { 
            error = "ran twice"
            return
        }
        defer {
            ran = true
            sem.signal()
        }

        switch response {
            case .error(let err): error = err
            case .success(let res): result = res
        }
    }
    sem.wait()

    XCTAssertNil(error, "Did not want to see this error: \(error!)")

    <test response>
}

I use a semaphore to block the main thread until the request is processed asynchronously; this works fine for all my other Alamofire requests -- but not this one. The test hangs.
(Note bene: Using active waiting does not change things.)

Using the debugger, I figured out that

  • all code that executes does so just fine but
  • encodingCompletion is never called.

Now my best guess is that DispatchQueue.main.async says, "execute this on the main thread when it has time" -- which it never will, since my test code is blocking there (and will run further tests, anyway).

I replaced it with self.queue.async and upload.delegate.queue.addOperation, two other queueing operations found in the same function. Then the test runs through but yields unexpected errors; my guess is that then, encodingCompletion is called too early.

There are several questions to ask here; an answer to any can solve my problem.

  1. Can I test such code differently so that DispatchQueue.main can get to other tasks?
  2. How can I use the debugger to find out which thread runs when?
  3. How can I adapt Alamofire at the critical position so that it does not require the main queue?

回答1:


As explained here, this is a bad "solution" as it introduces the possibility for deadlocks when requests are nested. I'm leaving this here for instructional purposes.


Changing

DispatchQueue.main.async {
    let encodingResult = MultipartFormDataEncodingResult.success(
            request: upload,
            streamingFromDisk: true,
            streamFileURL: fileURL
        )

    encodingCompletion?(encodingResult)
}

in SessionManager.swift to

self.queue.sync {
    ...
}

solves (read: works around) the problem.

I have no idea if this is a robust fix or anything; I have filed an issue.




回答2:


We should not block the main thread. XCTest has its own solution for waiting on asynchronous computations:

let expectation = self.expectation(description: "Operation should finish.")
operation(...) { response in
    ...
    expectation.fulfill()
}
waitForExpectations(timeout: self.timeout)

From the documentation:

Runs the run loop while handling events until all expectations are fulfilled or the timeout is reached. Clients should not manipulate the run loop while using this API.


Outside of XCTest, we can use a similar mechanism as XCTestCase.waitForExpectations() does:

var done = false
operation(...) { response in
    ...
    done = true
}

repeat {
    RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
} while !done

Note: This assumes that operation sends its work to the same queue itself is executed on. If it uses another queue, this won't work; but then the approach using DispatchSemaphore (see the question) does not cause a deadlock and can be used.

The implementation in XCTest does a lot more (multiple expectations, timeout, configurable sleep interval, etc.) but this is the basic mechanism.



来源:https://stackoverflow.com/questions/42644111/alamofire-never-calls-encodingcompletion-for-upload-with-multipartformdata-when

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!