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

如何在宿主App中跟踪共享扩展创建的后台上传任务进度并管控任务

解决方案

一、先解决-996错误(核心前提)

错误NSURLErrorBackgroundSessionWasDisconnected(-996)的本质是同一个后台Session ID被多个进程(宿主App/共享扩展)同时创建了活跃实例。你当前代码中宿主主动创建了扩展专属的backgroundSessionExtension,这会和共享扩展中正在运行的同ID Session直接冲突。

修正规则

  • 共享扩展:仅创建并使用专属的后台Session ID(如bgSessionIdExtension)
  • 宿主App:绝对不能主动创建扩展用的Session,而是通过系统API获取该ID对应的任务(系统会自动关联已存在的Session,无需手动实例化)

修改后的宿主端Session代码

// 宿主仅维护自身的上传Session,移除扩展Session的手动创建代码
private lazy var backgroundSession: URLSession = {
    return createBackgroundURLSession(with: FileUploadService.bgSessionId)
}()

二、宿主跟踪共享扩展的上传进度

共享扩展和宿主是独立进程,后台Session的代理回调只会触发在任务发起进程(扩展),因此需要通过跨进程数据同步实现进度跟踪。

最优实现:共享容器+Realm跨进程同步

  1. 确认共享容器配置:确保宿主和扩展已开启App Groups权限,且使用同一个sharedContainerIdentifier(你当前代码已配置,无需修改)
  2. Realm存储任务元数据:在共享容器中创建Realm数据库,存储每个上传任务的核心信息:
    • Session ID
    • 系统Task ID(task.taskIdentifier)
    • 上传进度(已上传字节/总字节)
    • 任务状态(等待中/上传中/已完成/已取消/失败)
  3. 扩展端实时更新进度:在共享扩展的URLSessionTaskDelegate回调中,同步更新Realm数据:
    // 扩展端代理方法:更新上传进度
    func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
        guard totalBytesExpectedToSend > 0 else { return }
        let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
        
        // 初始化共享容器的Realm配置
        let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppConstants.appGroupName)!
        let realmConfig = Realm.Configuration(fileURL: sharedContainerURL.appendingPathComponent("UploadTasks.realm"))
        
        do {
            let realm = try Realm(configuration: realmConfig)
            try realm.write {
                // 找到对应任务并更新进度
                if let uploadTask = realm.objects(UploadTask.self).filter("taskId == %@", task.taskIdentifier).first {
                    uploadTask.progress = progress
                    uploadTask.status = .uploading
                }
            }
        } catch {
            print("Realm更新进度失败:\(error)")
        }
    }
    
  4. 宿主端监听进度变化:宿主通过Realm的NotificationToken监听共享容器中任务数据的实时变化,同步更新UI:
    private var realmNotificationToken: NotificationToken?
    
    func setupProgressMonitor() {
        let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppConstants.appGroupName)!
        let realmConfig = Realm.Configuration(fileURL: sharedContainerURL.appendingPathComponent("UploadTasks.realm"), readOnly: true)
        
        do {
            let realm = try Realm(configuration: realmConfig)
            let uploadTasks = realm.objects(UploadTask.self)
            // 监听任务数据变化
            realmNotificationToken = uploadTasks.observe { [weak self] changes in
                switch changes {
                case .initial, .update:
                    // 更新UI展示最新进度
                    self?.refreshProgressUI()
                case .error(let error):
                    print("Realm监听错误:\(error)")
                }
            }
        } catch {
            print("Realm初始化失败:\(error)")
        }
    }
    
    // 页面销毁时销毁监听,避免内存泄漏
    deinit {
        realmNotificationToken?.invalidate()
    }
    

三、宿主对扩展任务执行取消/暂停操作

核心逻辑:通过系统API获取扩展的Session任务,直接调用任务的原生方法。

实现代码

// 统一处理扩展任务的工具方法
func handleExtensionUploadTasks(action: @escaping (URLSessionUploadTask) -> Void) {
    URLSession.shared.getTasksWithCompletionHandler { _, uploadTasks, _ in
        // 筛选出扩展专属Session的任务
        let extensionTasks = uploadTasks.filter {
            $0.session.configuration.identifier == FileUploadService.bgSessionIdExtension
        }
        extensionTasks.forEach(action)
    }
}

// 调用示例:取消所有扩展上传任务
func cancelAllExtensionTasks() {
    handleExtensionUploadTasks { $0.cancel() }
}

// 调用示例:暂停所有扩展上传任务
func suspendAllExtensionTasks() {
    handleExtensionUploadTasks { $0.suspend() }
}

// 调用示例:恢复所有暂停的扩展上传任务
func resumeAllExtensionTasks() {
    handleExtensionUploadTasks { $0.resume() }
}

注意事项

  • 暂停任务后,系统会保留任务状态,重启App/扩展时可通过getTasksWithCompletionHandler恢复任务,调用resume()即可继续上传
  • 取消任务后,任务会从系统后台Session中移除,需同步更新Realm中对应任务的状态为「已取消」

四、补充优化建议

  • 任务唯一标识:每个上传任务创建时,额外生成自定义UUID存储到Realm,避免系统taskIdentifier重复导致的匹配错误
  • 错误同步:在扩展端的urlSession(_:task:didCompleteWithError:)回调中,同步更新Realm中任务的错误信息和最终状态
  • 内存优化:宿主端的Realm监听NotificationToken必须在页面销毁时销毁,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 02:55:08