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" ],
崩溃原因分析
- 核心错误:JPEG图片是二进制格式,并非UTF-8编码的文本内容,
String(data: fileData, encoding: .utf8)会返回nil,强制解包!直接触发崩溃。 - 次要问题:
- 手动拼接
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
相关产品推荐
相关产品推荐

