Kotlin多平台项目中Swift端使用Kotlin Flow遭遇EXC_BAD_ACCESS错误的排查与解决咨询
解决KMM项目中Swift端使用CFlow时的
EXC_BAD_ACCESS错误 嗨,我来帮你排查这个问题!你遇到的EXC_BAD_ACCESS错误在Swift-Kotlin跨平台场景里,大多和内存管理漏洞、协程调度适配或者闭包引用问题有关。结合你的代码,我整理了几个关键修复点和优化建议:
1. 修复Swift闭包的循环引用问题
你的watch闭包里直接捕获了self,但没有做弱引用处理,这会导致ProfileViewModel和CFlow的协程形成循环引用——ViewModel持有repository,repository的Flow闭包又持有ViewModel,最终导致ViewModel无法被释放,内存泄漏后触发野指针错误。
修改后的Swift代码片段:
func authenticate(email: String, password: String) { guard isValidForm(email: email, password: password) else { return } // 先取消之前的流(避免重复请求) tokenFlowCloseable?.close() // 用[weak self]避免循环引用 tokenFlowCloseable = repository.getTokenCFlow(email: email, password: password).watch { [weak self] response in guard let self = self else { return } // 安全解包弱引用 // 确保UI更新在主线程(即使CFlow用了Main调度,双重保险) DispatchQueue.main.async { switch response.status { case .success: self.loading = false guard let token = response.data?.accessToken else { self.authentication = .authenticationFailed("Token获取失败") return } SecretStorage().saveToken(token: token) self.authentication = .authenticated case .loading: self.loading = true case .error: let errorMsg = response.error?.localizedDescription ?? "未知错误" print("请求错误:\(errorMsg)") self.authentication = .authenticationFailed(errorMsg) self.loading = false } } } }
2. 管理CFlow的Closeable生命周期
你之前没有持有watch方法返回的Closeable引用,当ViewModel销毁时,协程不会被取消,仍会尝试访问已释放的对象,这也是EXC_BAD_ACCESS的常见诱因。需要在ViewModel里持有Closeable,并在销毁时主动关闭。
添加Closeable持有与销毁逻辑:
class ProfileViewModel: ObservableObject { private let repository: ProfileRepository private var tokenFlowCloseable: Closeable? // 持有流的关闭引用 init(repository: ProfileRepository) { self.repository = repository } // ViewModel销毁时关闭协程 deinit { tokenFlowCloseable?.close() } // ...其他代码保持不变 }
3. 优化CFlow的协程Scope配置
你的CFlow用了临时创建的CoroutineScope(Dispatchers.Main + job),这种Scope的生命周期管理不够严谨,建议改用SupervisorJob和Dispatchers.Main.immediate,提升跨平台主线程适配的稳定性。
修改后的CFlow代码:
@InternalCoroutinesApi class CFlow<T>(private val origin: Flow<T>): Flow<T> by origin { fun watch(block: (T) -> Unit): Closeable { // 用SupervisorJob避免单个协程失败影响整个Scope,immediate确保主线程立即执行 val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) val job = origin.onEach { block(it) }.launchIn(scope) return object: Closeable { override fun close() { scope.cancel() // 取消整个Scope而非单个Job,更彻底 } } } } // 优化Flow转CFlow的逻辑,将普通Flow转为SharedFlow避免重复执行 @InternalCoroutinesApi fun <T> wrapSwift(flow: Flow<T>): CFlow<T> { val sharedFlow = if (flow is SharedFlow<*>) { flow as Flow<T> } else { flow.shareIn(CoroutineScope(Dispatchers.Default), SharingStarted.WhileSubscribed(5000)) } return CFlow(sharedFlow) }
4. 其他细节优化
- 避免强制解包:把代码里的
response!.data!.accessToken!改成可选绑定,防止意外崩溃; - 变量命名规范:Swift里变量名首字母小写(比如把
TokenResponse改成tokenResponse),符合语言习惯; - 依赖版本对齐:确保KMM项目中
kotlinx-coroutines-core和kotlinx-coroutines-ios的版本完全一致,避免跨平台兼容性问题。
调试建议
用Xcode的Memory Graph Debugger查看是否存在内存泄漏,或者在ViewModel的deinit方法和CFlow的close方法添加断点,确认对象是否被正确销毁,协程是否被及时取消。
内容的提问来源于stack exchange,提问作者Jhonata Ávila
相关产品推荐
相关产品推荐

