This problem occurred on MultipartFormDataBodyParameters.Part(data: Data)
.
The declaration code was like below:
struct MyFormRequest: Request {
let baseURL = URL(string: "https://example.com/")!
let method: HTTPMethod = .get
let path = "/"
typealias Response = Void
private let parts: [MultipartFormDataBodyParameters.Part]
init(images: [UIImage]) throws {
parts = try images.map {
guard let data = $0.jpegData() else { throw AppError.invalidSession }
return data
}.enumerated().map { (i, data) in
MultipartFormDataBodyParameters.Part(
data: data,
name: "images[]",
mimeType: "image/jpeg",
fileName: "imagae\(i).jpg")
}
}
var bodyParameters: BodyParameters? {
return MultipartFormDataBodyParameters(
parts: parts,
boundary: "0123456789abcdef")
}
func response(from object: Any, urlResponse: HTTPURLResponse) throws {
}
}
Then, call it like below:
func execute(images: [UIImage]) {
let request = MyFormRequest(images: images)
Session.send(request: request) { result in
if case .success = result { return }
// retry the request. (with the same instance)
Session.send(request: request) { result2 in
// it will be never success or failure!!!
}
}
}
I noticed this problem occured because of the class MultipartFormDataBodyParameters reads InputStream without rewinding or renewing stream.
# MultipartFormDataBodyParameters.swift:
case bodyRange:
if bodyPart.inputStream.streamStatus == .notOpen {
bodyPart.inputStream.open()
}
// this line never finished when calling `read` twice on same instance.
let readLength = bodyPart.inputStream.read(offsetBuffer, maxLength: availableLength)
sentLength += readLength
totalSentLength += readLength
So I resolved it by changing Request class like below finally.
struct MyFormRequest: Request {
let baseURL = URL(string: "https://example.com/")!
let method: HTTPMethod = .get
let path = "/"
typealias Response = Void
private let images: [Data]
init(images: [UIImage]) throws {
images = try images.map {
guard let data = $0.jpegData() else { throw AppError.invalidSession }
return data
}
// Do not create Part instance while initialization.
}
var bodyParameters: BodyParameters? {
// Recreate part instance for each request.
let parts = images.enumerated().map { (i, data) in
MultipartFormDataBodyParameters.Part(
data: data,
name: "images[]",
mimeType: "image/jpeg",
fileName: "imagae\(i).jpg")
}
return MultipartFormDataBodyParameters(
parts: parts,
boundary: "0123456789abcdef")
}
func response(from object: Any, urlResponse: HTTPURLResponse) throws {
}
}
To begin with, I think the request instance should not be reuse.
But in our project, we have to reuse it. (We wrap APIKit with RxSwift. When the retryable error (network connection error or somethink) occurred, to retry the request, we have to re-subscribe the same stream(Observable) with same request instance)
I hope this post can help someone.
Environment:
APIKit version 4.0.0, iOS 12.1.4, Xcode 10.2.1(10E1001)