You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS中POST请求上传图片遇空值解包崩溃问题求助

解决POST上传图片时的「Unexpectedly found nil while unwrapping an Optional value」崩溃问题

问题背景

在POST请求中上传图片文件路径时,始终触发致命错误「Unexpectedly found nil while unwrapping an Optional value」,错误发生在let fileContent = String(data: fileData, encoding: .utf8)!代码行。

现有POST请求代码

let parameters = [
  [
    "key": "description",
    "value": "Hello",
    "type": "text",
    "contentType": "text/plain"
  ],
  [
    "key": "photo",
    "src": "/path/to/file",
    "type": "file"
  ],
  [
    "key": "rate",
    "value": "{  
  \"availability\": 1,
  \"beauty\": 2,
  \"purity\": 3
}",
    "type": "text"
  ],
  [
    "key": "results",
    "value": "[{ 
  \"waste_id\":{\"name\": \"Крышечки\",
  \"unit_of_waste\": \"шт\"},
  \"amount\": 10
}]
",
    "type": "text"
  ],
  [
    "key": "report_status",
    "value": "Accept",
    "type": "text"
  ],
  [
    "key": "point_id",
    "value": "",
    "type": "text"
  ],
  [
    "key": "type_obj",
    "value": "route",
    "type": "text"
  ],
  [
    "key": "id_obj",
    "value": "",
    "type": "text"
  ]] as [[String: Any]]

let boundary = "Boundary-\(UUID().uuidString)"
var body = ""
var error: Error? = nil
for param in parameters {
  if param["disabled"] != nil { continue }
  let paramName = param["key"]!
  body += "--\(boundary)\r\n"
  body += "Content-Disposition:form-data; name=\"\(paramName)\""
  if param["contentType"] != nil {
    body += "\r\nContent-Type: \(param["contentType"] as! String)"
  }
  let paramType = param["type"] as! String
  if paramType == "text" {
    let paramValue = param["value"] as! String
    body += "\r\n\r\n\(paramValue)\r\n"
  } else {
    let paramSrc = param["src"] as! String
    let fileData = try NSData(contentsOfFile: paramSrc, options: []) as Data
    let fileContent = String(data: fileData, encoding: .utf8)!
    body += "; filename=\"\(paramSrc)\"\r\n"
      + "Content-Type: \"content-type header\"\r\n\r\n\(fileContent)\r\n"
  }
}
body += "--\(boundary)--\r\n";
let postData = body.data(using: .utf8)

var request = URLRequest(url: URL(string: "http://somehost:8000/report/create_report/")!,timeoutInterval: Double.infinity)
request.addValue("Bearer some key", forHTTPHeaderField: "Authorization")
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

request.httpMethod = "POST"
request.httpBody = postData

let task = URLSession.shared.dataTask(with: request) { data, response, error in 
  guard let data = data else {
    print(String(describing: error))
    return
  }
  print(String(data: data, encoding: .utf8)!)
}

task.resume()

图片选择与本地保存代码

extension ReportViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
        guard let image = info[.originalImage] as? UIImage else {
            dismiss(animated: true, completion: nil)
            return
        }
        
        // Store the selected image
        selectedImage = image
        
        dismiss(animated: true, completion: nil)
    }
    
    func saveImageToFile(_ image: UIImage) -> String? {
        guard let imageData = image.jpegData(compressionQuality: 1.0) else {
            print("Failed to convert image to JPEG data")
            return nil
        }
        
        let fileManager = FileManager.default
        let temporaryDirectory = NSTemporaryDirectory()
        let fileName = UUID().uuidString + ".jpeg"
        let fileURL = URL(fileURLWithPath: temporaryDirectory).appendingPathComponent(fileName)
        
        do {
            try imageData.write(to: fileURL)
            return fileURL.path
        } catch {
            print("Failed to save image to file: \(error)")
            return nil
        }
    }
}

图片路径传入请求体的代码

guard let image = selectedImage else {
            print("No image selected")
            return
        }
        
        guard let fileURL = saveImageToFile(image) else {
            print("Failed to save image to file")
            return
        }
        
        guard let filePath = fileURL.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
            print("Failed to encode file path")
            return
        }

           [
                "key": "photo",
                "src": filePath,
                "type": "file"
            ],

崩溃原因分析

  1. 核心错误:JPEG图片是二进制格式,并非UTF-8编码的文本内容,String(data: fileData, encoding: .utf8)会返回nil,强制解包!直接触发崩溃。
  2. 次要问题:
    • 手动拼接multipart/form-data时,将二进制文件转成字符串再拼接,会破坏原始二进制数据的完整性。
    • 本地文件路径不需要做URL编码,编码后可能导致文件无法读取。

修复方案(原生Swift实现)

1. 重构POST请求代码(直接操作Data类型)

func createReportRequest(with imagePath: String) {
    let boundary = "Boundary-\(UUID().uuidString)"
    guard let url = URL(string: "http://somehost:8000/report/create_report/") else {
        print("Invalid URL")
        return
    }
    var request = URLRequest(url: url, timeoutInterval: Double.infinity)
    request.addValue("Bearer some key", forHTTPHeaderField: "Authorization")
    request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    
    var bodyData = Data()
    
    // 辅助方法:添加文本参数
    func addTextParam(key: String, value: String, contentType: String? = nil) {
        bodyData.append("--\(boundary)\r\n".data(using: .utf8)!)
        bodyData.append("Content-Disposition: form-data; name=\"\(key)\"\r\n".data(using: .utf8)!)
        if let contentType = contentType {
            bodyData.append("Content-Type: \(contentType)\r\n".data(using: .utf8)!)
        }
        bodyData.append("\r\n".data(using: .utf8)!)
        bodyData.append(value.data(using: .utf8)!)
        bodyData.append("\r\n".data(using: .utf8)!)
    }
    
    // 辅助方法:添加文件参数
    func addFileParam(key: String, filePath: String, fileName: String, mimeType: String) {
        guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
            print("Failed to read file data")
            return
        }
        bodyData.append("--\(boundary)\r\n".data(using: .utf8)!)
        bodyData.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
        bodyData.append("Content-Type: \(mimeType)\r\n".data(using: .utf8)!)
        bodyData.append("Content-Transfer-Encoding: binary\r\n".data(using: .utf8)!)
        bodyData.append("\r\n".data(using: .utf8)!)
        bodyData.append(fileData)
        bodyData.append("\r\n".data(using: .utf8)!)
    }
    
    // 添加所有文本参数
    addTextParam(key: "description", value: "Hello", contentType: "text/plain")
    
    let rateJSON = """
    {
        "availability": 1,
        "beauty": 2,
        "purity": 3
    }
    """
    addTextParam(key: "rate", value: rateJSON)
    
    let resultsJSON = """
    [{
        "waste_id": {"name": "Крышечки", "unit_of_waste": "шт"},
        "amount": 10
    }]
    """
    addTextParam(key: "results", value: resultsJSON)
    
    addTextParam(key: "report_status", value: "Accept")
    addTextParam(key: "point_id", value: "")
    addTextParam(key: "type_obj", value: "route")
    addTextParam(key: "id_obj", value: "")
    
    // 添加图片文件
    let fileName = URL(fileURLWithPath: imagePath).lastPathComponent
    addFileParam(key: "photo", filePath: imagePath, fileName: fileName, mimeType: "image/jpeg")
    
    // 添加结束边界
    bodyData.append("--\(boundary)--\r\n".data(using: .utf8)!)
    
    request.httpBody = bodyData
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data else {
            print(String(describing: error))
            return
        }
        print(String(data: data, encoding: .utf8)!)
    }
    task.resume()
}

2. 修改图片路径传入逻辑(移除URL编码)

guard let image = selectedImage else {
    print("No image selected")
    return
}

guard let filePath = saveImageToFile(image) else {
    print("Failed to save image to file")
    return
}

// 本地文件路径无需URL编码,直接传入请求方法
createReportRequest(with: filePath)

关键修复点说明

  • 直接操作Data类型构建请求体,避免将二进制图片转成字符串导致的数据丢失与崩溃。
  • 使用辅助方法拆分文本参数与文件参数的拼接逻辑,降低手动拼接multipart/form-data的出错概率。
  • 移除本地文件路径的URL编码步骤,确保文件能被正确读取。

内容的提问来源于stack exchange,提问作者Alexander Chebotarev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 09:32:35