视图控制器弹出后需触发S3上传回调及XMPP消息发送
基于XMPP实现的聊天功能运行正常,当前开发图片上传功能,流程为:将图片上传至S3存储桶,上传完成后通过XMPP将S3图片URL作为消息发送给对方。该功能在停留在聊天页面时可正常运行,但如果在图片上传过程中退出聊天视图控制器(VC),控制器会被释放,导致S3上传完成后的回调无法触发,进而无法调用sendMessage方法发送消息。需要实现即使切换到其他页面,仍能触发上传完成回调并完成消息发送。
@IBAction func didPressMedia(_ sender: Any) { ImagePickerManager().pickImage(self) { [weak self] pickedImage in guard let self = self else { return } let compressedImage = pickedImage.resizedImage(newSize: CGSize(width: 120, height: 120)) let messageId = XMPPHelper.shared.xmppStream.generateUUID let chat = ChatListModel() chat.roomId = "" chat.message = "" chat.messageId = messageId chat.time = String(Date().currentTimeMillis()) chat.to = self.viewModel.otherUser chat.from = self.viewModel.myJid ?? "" chat.type = "chat/image" chat.imageLocalPath = messageId chat.msgDeliveryStatus = MsgDeliveryStatus.sending.rawValue chat.isSeenSync = true chat.isUploading = "pending" self.viewModel.setChatDataInRealm(item: chat, isAfterPickingImage: true) //This is completion handler which get called after image upload to s3. self.viewModel.didImageUploaded = { [weak self] isImgUploaded , imgUrl, imgMessageId in guard let self = self else { return } if isImgUploaded { print("success uploading image") self.viewModel.sendMessage(message: imgUrl, isImage: true, messageId: imgMessageId) // send image url as message to other user }else{ print("error uploading image") self.viewModel.updateRealmAfterImageUpload(uploadStatus: "failed", uploadedUrl: "", messsageId: imgMessageId) } } self.viewModel.uploadImage(localPath: "\(Helper.shared.getDocumentDirectoryPath(filename: messageId)).png" ,image: compressedImage, messageId: messageId) } }
核心思路是让上传逻辑和后续的消息发送逻辑脱离ViewController的生命周期,确保即使VC被释放,上传任务和回调处理依然能正常执行。
将上传后的回调逻辑移至ViewModel内部
移除ViewModel中依赖VC的didImageUploaded闭包,直接在ViewModel的uploadImage方法内部处理上传完成后的逻辑(发送消息、更新Realm)。这样不需要VC参与后续流程,即使VC被释放,ViewModel只要存活就能完成后续操作。重构后的ViewModel示例:
class ChatViewModel { // 移除原有的didImageUploaded闭包 func uploadImage(localPath: String, image: UIImage, messageId: String) { // 执行S3上传操作(以AWS SDK为例) let uploadRequest = AWSS3TransferManagerUploadRequest() uploadRequest?.bucket = "your-bucket-name" uploadRequest?.key = "images/\(messageId).png" uploadRequest?.body = URL(fileURLWithPath: localPath) uploadRequest?.contentType = "image/png" AWSS3TransferManager.default().upload(uploadRequest!).continueWith { [weak self] task in guard let self = self else { return nil } if let error = task.error { // 上传失败,更新Realm状态 self.updateRealmAfterImageUpload(uploadStatus: "failed", uploadedUrl: "", messsageId: messageId) print("Image upload failed: \(error.localizedDescription)") } else { // 上传成功,获取S3文件URL并发送消息 let imgUrl = "https://your-bucket-name.s3.your-region.amazonaws.com/images/\(messageId).png" self.sendMessage(message: imgUrl, isImage: true, messageId: messageId) // 更新Realm状态为成功 self.updateRealmAfterImageUpload(uploadStatus: "success", uploadedUrl: imgUrl, messsageId: messageId) print("Image uploaded successfully") } return nil } } }简化ViewController中的代码
VC只需要负责选择图片、创建聊天记录模型并保存到Realm,然后调用ViewModel的上传方法即可,无需设置回调:@IBAction func didPressMedia(_ sender: Any) { ImagePickerManager().pickImage(self) { pickedImage in let compressedImage = pickedImage.resizedImage(newSize: CGSize(width: 120, height: 120)) let messageId = XMPPHelper.shared.xmppStream.generateUUID let chat = ChatListModel() chat.roomId = "" chat.message = "" chat.messageId = messageId chat.time = String(Date().currentTimeMillis()) chat.to = self.viewModel.otherUser chat.from = self.viewModel.myJid ?? "" chat.type = "chat/image" chat.imageLocalPath = messageId chat.msgDeliveryStatus = MsgDeliveryStatus.sending.rawValue chat.isSeenSync = true chat.isUploading = "pending" self.viewModel.setChatDataInRealm(item: chat, isAfterPickingImage: true) // 直接调用上传,无需绑定回调到VC self.viewModel.uploadImage(localPath: "\(Helper.shared.getDocumentDirectoryPath(filename: messageId)).png", image: compressedImage, messageId: messageId) } }确保ViewModel生命周期稳定
要保证ViewModel不会随VC一起被释放,可以将ViewModel交由全局的聊天管理类持有,或者使用单例模式(如果是全局聊天逻辑),避免ViewModel被提前销毁。配置后台任务延长上传时间
如果上传可能在APP进入后台时完成,需要使用UIApplication的后台任务API延长执行时间,避免系统终止上传任务:func uploadImage(localPath: String, image: UIImage, messageId: String) { var backgroundTaskId: UIBackgroundTaskIdentifier = .invalid backgroundTaskId = UIApplication.shared.beginBackgroundTask { UIApplication.shared.endBackgroundTask(backgroundTaskId) backgroundTaskId = .invalid } // 执行S3上传操作... // 上传完成后结束后台任务 defer { if backgroundTaskId != .invalid { UIApplication.shared.endBackgroundTask(backgroundTaskId) } } }
内容的提问来源于stack exchange,提问作者rauldhasmana

