当主线程正在等待它运行时,Alamofire从不使用MultipartFormData调用encodingCompletion进行上传

时间:2017-03-07 09:06:17

标签: swift asynchronous concurrency alamofire

我有这种形式的代码:

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

测试代码如下:

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

我使用信号量来阻塞主线程,直到异步处理请求为止;这适用于我所有其他Alamofire请求 - 但不是这个。测试挂起。
(注意事项:使用active waiting不会改变事情。)

使用调试器,我想出了

现在我最好的猜测是DispatchQueue.main.async说,“当它有时间时在主线程上执行它” - 它永远不会,因为我的测试代码在那里阻塞(并且将进行进一步的测试,无论如何)。

我将其替换为self.queue.asyncupload.delegate.queue.addOperation,在同一个函数中找到了另外两个排队操作。然后测试运行但产生意外错误;我的猜测是,encodingCompletion太早称为

这里有几个问题要问;任何答案都可以解决我的问题。

  1. 我可以不同地测试这些代码,以便DispatchQueue.main可以进入其他任务吗?
  2. 如何使用调试器找出何时运行的线程?
  3. 如何调整Alamofire at the critical position以使其不需要主队列?

2 个答案:

答案 0 :(得分:0)

正如here所解释的那样,这是一个糟糕的“解决方案”,因为它会在嵌套请求时引入死锁的可能性。我出于教学目的离开这里。

更改

self.queue.sync {
    ...
}

SessionManager.swift

{{1}}

解决(阅读:解决)问题。

我不知道这是一个强大的修复还是其他任何东西;我有filed an issue

答案 1 :(得分:0)

我们不应该阻止主线程。 XCTest有自己的等待异步计算的解决方案:

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

来自the documentation

  

在处理事件时运行运行循环,直到满足所有期望或达到超时。使用此API时,客户端不应操纵运行循环。

在XCTest之外,我们可以使用与XCTestCase.waitForExpectations()类似的机制:

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

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

注意:这假定operation将其工作发送到同一队列本身执行。如果它使用另一个队列,这将不起作用;但是后来使用DispatchSemaphore(参见the question)的方法不会导致死锁并且可以使用。

XCTest中的实现做了很多(多个期望,超时,可配置的睡眠间隔等),但这是基本机制。