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

视图控制器弹出后需触发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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:55:00