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

Kotlin Multiplatform(iOS端)文件保存失败问题排查与解决咨询

iOS端KMP写入Cache目录失败的解决方案

问题根源

你遇到的两个问题本质都是路径格式错误:

  • 传入的路径带file:/协议前缀,Okio的FileSystem会把它识别成非本地文件路径,实际访问的是App的只读Bundle目录,因此抛出「Read-only file system」错误。
  • 因为路径格式不对,fileSystem.exists(parentDir)始终返回false,所以即便目录真实存在,代码还是会重复尝试创建。

修复步骤

1. 修正iOS端的路径传入逻辑

在iOS端获取Cache目录时,直接传本地路径字符串(不要带file://前缀):

  • Swift代码示例:
if let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
    let targetPath = cacheDir.appendingPathComponent("syncFiles/sync-proxy.json").path
    // 把targetPath传给Kotlin层
}
  • Kotlin/Native代码示例:
val cacheDir = NSFileManager.defaultManager.URLForDirectory(
    NSCachesDirectory,
    NSUserDomainMask,
    null,
    true,
    null
)?.path ?: error("Failed to get cache directory")
val targetPath = cacheDir + "/syncFiles/sync-proxy.json"

2. 优化Kotlin端的文件写入代码

确保使用正确的FileSystem实例(Okio的FileSystem.SYSTEM),并优化错误处理逻辑:

import okio.FileSystem
import okio.Path.Companion.toPath
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import io.ktor.http.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.readFully

suspend fun HttpResponse.saveBodyToFile(fileSystem: FileSystem = FileSystem.SYSTEM, outputPathStr: String): Boolean {
    val outputFile = outputPathStr.toPath()
    return withContext(Dispatchers.IO) {
        var success = false
        try {
            // 先删除旧文件
            if (fileSystem.exists(outputFile)) {
                fileSystem.delete(outputFile)
            }
            
            val parentDir = outputFile.parent ?: return@withContext false
            // 确保父目录存在(路径修正后,exists判断会正常工作)
            if (!fileSystem.exists(parentDir)) {
                fileSystem.createDirectories(parentDir)
            }

            val byteReadChannel: ByteReadChannel = body()
            fileSystem.sink(outputFile).buffer().use { sink ->
                byteReadChannel.readFully(sink)
            }

            success = fileSystem.exists(outputFile) && fileSystem.metadata(outputFile).size > 0
        } catch (e: Exception) {
            // 统一捕获IO异常,清理无效文件
            runCatching { fileSystem.delete(outputFile) }
            Logger.e(e) { "Failed to save response to ${outputFile.name}" }
            Logger.e { "Error message: ${e.message}" }
        }
        success
    }
}

3. 关键说明

  • 必须用Dispatchers.IO调度文件IO操作,避免阻塞主线程。
  • 增加文件大小校验,确保写入的文件是有效的(不仅仅是存在)。
  • 统一异常捕获逻辑,避免重复代码。

验证方法

调用时传入修正后的本地路径字符串,比如:

// 假设从iOS端拿到的路径是 "/Users/.../Library/Caches/syncFiles/sync-proxy.json"
response.saveBodyToFile(outputPathStr = validCachePath)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:35:00