Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/117.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何在Swift中使用Codable和URLSession.shared.uploadTask(多部分/表单数据)上载图像文件?_Ios_Swift_Alamofire_Multipartform Data_Codable - Fatal编程技术网

Ios 如何在Swift中使用Codable和URLSession.shared.uploadTask(多部分/表单数据)上载图像文件?

Ios 如何在Swift中使用Codable和URLSession.shared.uploadTask(多部分/表单数据)上载图像文件?,ios,swift,alamofire,multipartform-data,codable,Ios,Swift,Alamofire,Multipartform Data,Codable,我想使用特定的URL端点将图像文件上载到后端服务器。使用Alamofire的上传请求作为multipartFormData,我可以很容易地做到这一点。然而,我想摆脱Alamofire,以尽量减少对第三方框架的依赖。 以下是Alamofire代码,它可以工作: func uploadRequestAlamofire(parameters: [String: Any], imageData: Data?, completion: @escaping(CustomError?) -> Void

我想使用特定的URL端点将图像文件上载到后端服务器。使用Alamofire的上传请求作为multipartFormData,我可以很容易地做到这一点。然而,我想摆脱Alamofire,以尽量减少对第三方框架的依赖。 以下是Alamofire代码,它可以工作:

func uploadRequestAlamofire(parameters: [String: Any], imageData: Data?, completion: @escaping(CustomError?) -> Void ) {

let url = imageUploadEndpoint!

let headers: HTTPHeaders = ["X-User-Agent": "ios",
                            "Accept-Language": "en",
                            "Accept": "application/json",
                            "Content-type": "multipart/form-data",
                            "ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""]

Alamofire.upload(multipartFormData: { (multipartFormData) in
    for (key, value) in parameters {
        multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: key as String)
    }

    if let data = imageData {
        multipartFormData.append(data, withName: "file", fileName: "image.png", mimeType: "image/jpg")
    }

}, usingThreshold: UInt64.init(), to: url, method: .post, headers: headers) { (result) in
    switch result {
    case .success(let upload, _, _):
        upload.responseJSON { response in

            completion(CustomError(errorCode: response.response!.statusCode))

            print("Succesfully uploaded")
        }
    case .failure(let error):
        print("Error in upload: \(error.localizedDescription)")

    }
}
}
以下是URLSession上载任务,该任务不起作用:

func requestNativeImageUpload(imageData: Data, orderExtId: String) {

var request = URLRequest(url: imageUploadEndpoint!)
request.httpMethod = "POST"
request.timeoutInterval = 10

    request.allHTTPHeaderFields = [
        "X-User-Agent": "ios",
        "Accept-Language": "en",
        "Accept": "application/json",
        "Content-type": "multipart/form-data",
        "ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""
    ]

let body = OrderUpload(order_ext_id: orderExtId, file: imageData)

do {
    request.httpBody = try encoder.encode(body)
} catch let error {
    print(error.localizedDescription)
}

let session = URLSession.shared



session.uploadTask(with: request, from: imageData)  { data, response, error in
    guard let response = response as? HTTPURLResponse else { return }

    print(response)
    if error != nil {
        print(error!.localizedDescription)
    }


    }.resume()
}
这是我调用Alamofire和URLSession方法的方式:

uploadRequestAlamofire(parameters: ["order_ext_id": order_ext_id, "file": "image.jpg"], imageData: uploadImage) { [weak self] response in } 

requestNativeImageUpload(imageData: uploadImage!, orderExtId: order_ext_id)
以下是后端服务器希望在请求正文中接收的内容:

let order_ext_id: String
let description: String
let file: string($binary)
这是为请求的httpBody编码的可编码结构

struct OrderUpload: Codable {
    let order_ext_id: String
    let description: String 
    let file: String
}
虽然在这个演示中,我的方法可能不完全合适,并且我不处理响应状态代码,但是Alamofire方法工作得很好

为什么URLSession不能工作

  • 标题中的内容类型错误。它应该是这样的:
  • 您需要基于对象中的每个字段形成主体,就像您在Alamofire示例中向multipartFormData添加值一样(您在那里使用了字典)
  • 向数据中添加字符串的帮助器:

    extension NSMutableData {
        func appendString(_ string: String) {
            let data = string.data(using: .utf8)
            append(data!)
        }
    }
    
  • 标题中的内容类型错误。它应该是这样的:
  • 您需要基于对象中的每个字段形成主体,就像您在Alamofire示例中向multipartFormData添加值一样(您在那里使用了字典)
  • 向数据中添加字符串的帮助器:

    extension NSMutableData {
        func appendString(_ string: String) {
            let data = string.data(using: .utf8)
            append(data!)
        }
    }
    

    我终于找到了解决办法。 来源是:。 在我的特定情况下,我需要提供orderExtId作为后端服务器接受我的映像的参数。根据后端的要求,您的案例可能会有所不同

    func requestNativeImageUpload(image: UIImage, orderExtId: String) {
    
        guard let url = imageUploadEndpoint else { return }
        let boundary = generateBoundary()
        var request = URLRequest(url: url)
    
        let parameters = ["order_ext_id": orderExtId]
    
        guard let mediaImage = Media(withImage: image, forKey: "file") else { return }
    
        request.httpMethod = "POST"
    
        request.allHTTPHeaderFields = [
                    "X-User-Agent": "ios",
                    "Accept-Language": "en",
                    "Accept": "application/json",
                    "Content-Type": "multipart/form-data; boundary=\(boundary)",
                    "ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""
                ]
    
        let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
        request.httpBody = dataBody
    
        let session = URLSession.shared
        session.dataTask(with: request) { (data, response, error) in
            if let response = response {
                print(response)
            }
    
            if let data = data {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    print(json)
                } catch {
                    print(error)
                }
            }
            }.resume()
    }
    
    
    func generateBoundary() -> String {
        return "Boundary-\(NSUUID().uuidString)"
    }
    
    func createDataBody(withParameters params: [String: String]?, media: [Media]?, boundary: String) -> Data {
    
        let lineBreak = "\r\n"
        var body = Data()
    
        if let parameters = params {
            for (key, value) in parameters {
                body.append("--\(boundary + lineBreak)")
                body.append("Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
                body.append("\(value + lineBreak)")
            }
        }
    
        if let media = media {
            for photo in media {
                body.append("--\(boundary + lineBreak)")
                body.append("Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.fileName)\"\(lineBreak)")
                body.append("Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
                body.append(photo.data)
                body.append(lineBreak)
            }
        }
    
        body.append("--\(boundary)--\(lineBreak)")
    
        return body
    }
    
    
    extension Data {
        mutating func append(_ string: String) {
            if let data = string.data(using: .utf8) {
                append(data)
            }
        }
    }
    
    
    struct Media {
        let key: String
        let fileName: String
        let data: Data
        let mimeType: String
    
        init?(withImage image: UIImage, forKey key: String) {
            self.key = key
            self.mimeType = "image/jpg"
            self.fileName = "\(arc4random()).jpeg"
    
            guard let data = image.jpegData(compressionQuality: 0.5) else { return nil }
            self.data = data
        }
    }
    

    我终于找到了解决办法。 来源是:。 在我的特定情况下,我需要提供orderExtId作为后端服务器接受我的映像的参数。根据后端的要求,您的案例可能会有所不同

    func requestNativeImageUpload(image: UIImage, orderExtId: String) {
    
        guard let url = imageUploadEndpoint else { return }
        let boundary = generateBoundary()
        var request = URLRequest(url: url)
    
        let parameters = ["order_ext_id": orderExtId]
    
        guard let mediaImage = Media(withImage: image, forKey: "file") else { return }
    
        request.httpMethod = "POST"
    
        request.allHTTPHeaderFields = [
                    "X-User-Agent": "ios",
                    "Accept-Language": "en",
                    "Accept": "application/json",
                    "Content-Type": "multipart/form-data; boundary=\(boundary)",
                    "ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""
                ]
    
        let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
        request.httpBody = dataBody
    
        let session = URLSession.shared
        session.dataTask(with: request) { (data, response, error) in
            if let response = response {
                print(response)
            }
    
            if let data = data {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    print(json)
                } catch {
                    print(error)
                }
            }
            }.resume()
    }
    
    
    func generateBoundary() -> String {
        return "Boundary-\(NSUUID().uuidString)"
    }
    
    func createDataBody(withParameters params: [String: String]?, media: [Media]?, boundary: String) -> Data {
    
        let lineBreak = "\r\n"
        var body = Data()
    
        if let parameters = params {
            for (key, value) in parameters {
                body.append("--\(boundary + lineBreak)")
                body.append("Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
                body.append("\(value + lineBreak)")
            }
        }
    
        if let media = media {
            for photo in media {
                body.append("--\(boundary + lineBreak)")
                body.append("Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.fileName)\"\(lineBreak)")
                body.append("Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
                body.append(photo.data)
                body.append(lineBreak)
            }
        }
    
        body.append("--\(boundary)--\(lineBreak)")
    
        return body
    }
    
    
    extension Data {
        mutating func append(_ string: String) {
            if let data = string.data(using: .utf8) {
                append(data)
            }
        }
    }
    
    
    struct Media {
        let key: String
        let fileName: String
        let data: Data
        let mimeType: String
    
        init?(withImage image: UIImage, forKey key: String) {
            self.key = key
            self.mimeType = "image/jpg"
            self.fileName = "\(arc4random()).jpeg"
    
            guard let data = image.jpegData(compressionQuality: 0.5) else { return nil }
            self.data = data
        }
    }
    

    对我来说不太合适,但是我能够基于您的解决方案找到更好的实现,例如,我使用数据而不是NSMutableData。看看我的答案。对我来说不太合适,但是我能够根据您的解决方案找到更好的实现,例如,我使用数据而不是NSMutableData。看看我的答案。可能的重复