iOS中如何结合REST API与URLSession uploadTask实现后台数据上传?
老哥,我来帮你搞定这个iOS后台上传的问题!之前我也做过类似的需求,用URLSession的uploadTask结合Parse REST API实现后台上传完全可行,我给你一步步拆解,附上可以直接复用的代码示例。
实现iOS后台上传的核心步骤
1. 配置支持后台的URLSession
关键的第一步是创建后台专用的会话配置,普通会话在APP进入后台后会被系统挂起,而后台会话会由系统接管执行任务,即使APP被终止也能继续。
// 创建后台会话配置,标识符必须唯一(建议用APP反向域名+功能标识) let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.yourcompany.parse.background.upload") // 允许任务完成后唤醒APP(可选,根据你的需求决定是否需要回调) backgroundConfig.sessionSendsLaunchEvents = true // 创建会话并指定代理,代理用来处理上传结果和进度 let backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: OperationQueue())
别忘了让你的类遵守URLSessionTaskDelegate和URLSessionDataDelegate协议哦。
2. 把本地数据转换成Parse REST要求的格式
Parse的REST API对数据格式有特定要求,比如经纬度要转成GeoPoint类型,图片要转成File类型。假设你的本地数据结构是这样的:
// 模拟你的本地数据模型 struct LocalRecord { let latitude: Double let longitude: Double let image: UIImage? let note: String? // 其他自定义字段 }
写个转换方法把本地数据转成Parse能识别的JSON:
func convertToParseJSON(from record: LocalRecord) -> Data? { var parseObject: [String: Any] = [ // 处理经纬度:Parse要求GeoPoint类型必须带__type字段 "location": [ "__type": "GeoPoint", "latitude": record.latitude, "longitude": record.longitude ] ] // 添加其他自定义字段 if let note = record.note { parseObject["note"] = note } // 处理图片:转成Base64字符串,符合Parse的File格式 if let image = record.image, let imageData = image.jpegData(compressionQuality: 0.8) { parseObject["uploadedImage"] = [ "__type": "File", "name": "user_upload_\(UUID().uuidString).jpg", // 用UUID避免重名 "base64": imageData.base64EncodedString() ] } return try? JSONSerialization.data(withJSONObject: parseObject) }
注意:如果图片很大,Base64会让数据体积增加30%左右,这时候建议先把图片单独上传到Parse的
/files端点,拿到返回的文件URL后,再把URL关联到你的对象里,这样更高效。
3. 构造Parse REST API请求
接下来要构造符合Parse要求的POST请求,必须设置认证头和Content-Type:
func createParseUploadRequest(with jsonData: Data) -> URLRequest? { // 替换成你的Parse服务器地址和目标类名 guard let url = URL(string: "https://your-parse-server-domain/classes/YourTargetClassName") else { print("无效的Parse服务器URL") return nil } var request = URLRequest(url: url) request.httpMethod = "POST" // 设置Parse的认证信息(替换成你自己的APP ID和REST Key) request.setValue("YOUR_PARSE_APPLICATION_ID", forHTTPHeaderField: "X-Parse-Application-Id") request.setValue("YOUR_PARSE_REST_API_KEY", forHTTPHeaderField: "X-Parse-REST-API-Key") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = jsonData return request }
4. 创建并启动后台UploadTask
现在可以用上面的请求和数据创建后台上传任务了,启动后系统会自动在后台处理:
// 假设你从本地数据库拿到了一条待上传的记录 let localRecord = LocalRecord(latitude: 39.9042, longitude: 116.4074, image: UIImage(named: "test_photo"), note: "北京天安门") // 转换数据并构造请求 guard let jsonData = convertToParseJSON(from: localRecord), let uploadRequest = createParseUploadRequest(with: jsonData) else { print("数据转换或请求构造失败") return } // 创建后台上传任务 let uploadTask = backgroundSession.uploadTask(with: uploadRequest, from: jsonData) // 启动任务 uploadTask.resume()
5. 处理后台会话的代理回调
后台任务的结果需要通过代理方法获取,同时还要处理系统唤醒APP的情况:
实现代理方法
extension YourViewController: URLSessionTaskDelegate { // 上传任务完成(成功或失败都会回调) func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { print("上传失败:\(error.localizedDescription)") // 这里可以把这条记录标记为未上传,下次APP启动时重试 } else { print("上传成功!任务ID:\(task.taskIdentifier)") // 上传成功后,记得从本地数据库删除这条记录 } } // 可选:监听上传进度 func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { let progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend) print("当前上传进度:\(String(format: "%.1f", progress * 100))%") } // 后台会话所有任务完成时的回调(只有设置了sessionSendsLaunchEvents才会触发) func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { DispatchQueue.main.async { // 通知系统任务已完成,否则系统会认为APP还在处理后台任务 if let appDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.backgroundSessionCompletionHandler?() appDelegate.backgroundSessionCompletionHandler = nil } } } }
在AppDelegate中保存Completion Handler
当后台任务完成后,系统会唤醒APP,这时候需要调用系统提供的completion handler来告诉系统任务已处理完毕:
class AppDelegate: UIResponder, UIApplicationDelegate { var backgroundSessionCompletionHandler: (() -> Void)? func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { // 保存completion handler,等所有后台任务处理完再调用 backgroundSessionCompletionHandler = completionHandler } }
关键注意事项
- 后台会话的标识符必须全局唯一,不能和其他APP重复,也不能在你的APP内创建多个相同标识符的会话。
- 如果上传失败(比如网络中断),一定要在本地数据库标记这条记录,下次APP启动时自动重试,避免数据丢失。
- 后台任务有时间限制,系统会尽量完成任务,但如果任务耗时太长(比如上传超大文件),可能会被终止,建议拆分大任务为多个小任务。
- 不需要额外申请后台权限(比如Background Fetch),URLSession的后台上传是系统原生支持的功能。
内容的提问来源于stack exchange,提问作者naf123
相关产品推荐
相关产品推荐

