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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 16:52:43