当主线程等待它运行时,Alamofire 从不调用 encodingCompletion 以使用 MultipartFormData 上传
Posted
技术标签:
【中文标题】当主线程等待它运行时,Alamofire 从不调用 encodingCompletion 以使用 MultipartFormData 上传【英文标题】:Alamofire never calls encodingCompletion for upload with MultipartFormData when main thread is waiting for it to run 【发布时间】:2017-03-07 09:06:17 【问题描述】:我有这种形式的代码:
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 不会改变任何事情。)
使用调试器,我发现
所有执行的代码都很好,但是encodingCompletion
is never called.
现在我最好的猜测是DispatchQueue.main.async
说,“有时间的时候在主线程上执行它”——它永远不会,因为我的测试代码在那里阻塞(并且会运行进一步的测试,无论如何)。
我用self.queue.async
和upload.delegate.queue.addOperation
替换了它,这两个队列操作在同一个函数中找到。然后测试通过但产生意外错误;我的猜测是,encodingCompletion
被称为为时过早。
这里有几个问题要问;任何问题的答案都可以解决我的问题。
-
我能否以不同的方式测试此类代码,以便
DispatchQueue.main
可以执行其他任务?
如何使用调试器找出运行的线程是什么时候?
如何调整Alamofire at the critical position 使其不需要主队列?
【问题讨论】:
【参考方案1】:正如here 解释的那样,这是一个糟糕的“解决方案”,因为它在请求嵌套时引入了死锁的可能性。出于教学目的,我将其留在这里。
变化
DispatchQueue.main.async
let encodingResult = MultipartFormDataEncodingResult.success(
request: upload,
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
在SessionManager.swift到
self.queue.sync
...
解决(阅读:解决)问题。
我不知道这是否是一个强大的修复或任何东西;我有filed an issue。
【讨论】:
【参考方案2】:我们不应该阻塞主线程。 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 中的实现还有很多功能(多重期望、超时、可配置的睡眠间隔等),但这是基本机制。
【讨论】:
以上是关于当主线程等待它运行时,Alamofire 从不调用 encodingCompletion 以使用 MultipartFormData 上传的主要内容,如果未能解决你的问题,请参考以下文章