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

Swift中JSON播放列表保存后加载提示文件不存在的问题排查

播放列表JSON文件保存后加载提示不存在的问题排查

我写了一个将播放列表保存到本地JSON文件的函数,但后续解析该文件时,Xcode提示指定路径下的文件不存在,可文件实际确实存在。

相关代码

func savePlaylistData(lista: PlayListModel, _ playlistData: [PlaylistGroup: [PlaylistItem]], to filename: String) async throws {
    // 创建临时字典,用字符串作为键
    var tempDict: [String: [PlaylistItem]] = [:]
    
    // 将PlaylistGroup键转换为字符串键
    for (key, value) in playlistData {
        tempDict[key.groupName] = value
    }
    
    // 编码字典为JSON
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted // 让JSON更易读
    encoder.dateEncodingStrategy = .iso8601 // 确保日期正确编码
    
    // 在后台线程执行JSON编码和文件写入
    try await withCheckedThrowingContinuation { continuation in
            do {
                let jsonData = try encoder.encode(tempDict)
                
                // 将JSON数据写入带json后缀的文件
                let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename).appendingPathExtension("json")
                try jsonData.write(to: fileURL)
                
                print("数据已保存到 \(fileURL.path)")
                continuation.resume()
                
                // 更新PlayListModel
                lista.haveFile = true
                lista.playlistUrl = fileURL.path
                print("已更新PlayListModel,路径为 \(fileURL.path)")
                
                // 假设modelContext已正确配置
                do {
                    self.modelContext.insert(lista)
                    try modelContext.save()
                } catch {
                    print("保存PlayListModel失败: \(error)")
                }
                
            } catch {
                continuation.resume(throwing: error)
            }
      
    }
}

func loadPlaylistData(from url: URL) async throws -> [PlaylistGroup: [PlaylistItem]] {
    // 调试:打印URL
    
    // 调试:检查文件是否存在
    if FileManager.default.fileExists(atPath: url.path) {
        print("文件存在于 \(url.path)")
    } else {
        print("文件不存在于 \(url.path)")
        throw NSError(domain: "FileError", code: 404, userInfo: [NSLocalizedDescriptionKey: "文件不存在于 \(url.path)"])
    }
    
    // 调试:检查文件可读性
    do {
        let fileAttributes = try FileManager.default.attributesOfItem(atPath: url.path)
        print("文件属性: \(fileAttributes)")
    } catch {
        print("获取文件属性失败: \(error.localizedDescription)")
        throw NSError(domain: "FileError", code: 403, userInfo: [NSLocalizedDescriptionKey: "无法读取 \(url.path) 的文件属性"])
    }

    do {
        // 从文件读取JSON数据
        print("正在从 \(url) 读取JSON数据")
        let jsonData = try Data(contentsOf: url)
        
        // 将JSON数据解码为带字符串键的临时字典
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601 // 确保日期正确解码
        
        let tempDict: [String: [PlaylistItem]]
        do {
            tempDict = try decoder.decode([String: [PlaylistItem]].self, from: jsonData)
        } catch let decodeError {
            print("解码JSON数据失败: \(decodeError.localizedDescription)")
            throw decodeError
        }
        
        // 将临时字典转换回带PlaylistGroup键的原始格式
        var playlistData: [PlaylistGroup: [PlaylistItem]] = [:]
        for (key, value) in tempDict {
            let group = PlaylistGroup(groupName: key)
            playlistData[group] = value
        }
        
        return playlistData
    } catch let dataError {
        print("加载并解码文件失败: \(dataError.localizedDescription)")
        throw dataError
    }
}

问题原因分析

  • 临时目录的自动清理机制:iOS的临时目录(FileManager.default.temporaryDirectory)会被系统自动清理——当APP退到后台、系统磁盘空间不足时,文件可能被删除。你看到的"实际存在"可能是保存后短时间内的状态,加载时文件已被系统清理。
  • 路径存储的不准确性:你把fileURL.path(字符串格式的路径)存在lista.playlistUrl中,APP重启后临时目录的路径可能发生变化,旧的字符串路径会指向错误位置;另外,路径中的特殊字符可能导致字符串转URL时出现编码问题,生成无效的URL。
  • Core Data操作的线程不安全:在savePlaylistData中,你在后台线程的闭包里直接更新modelContext(Core Data上下文),而Core Data上下文默认是线程绑定的,非主线程操作可能导致数据未及时同步,加载时拿到的路径不是最新的。

解决办法

  1. 更换持久化存储目录
    放弃临时目录,改用文档目录(不会被系统自动清理),修改保存时的URL生成代码:

    guard let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
        throw NSError(domain: "FileError", code: 500, userInfo: [NSLocalizedDescriptionKey: "无法获取文档目录"])
    }
    let fileURL = docDir.appendingPathComponent(filename).appendingPathExtension("json")
    
  2. 存储URL而非字符串路径
    将PlayListModel中的playlistUrl类型改为URL(Core Data中对应Transformable类型),避免字符串转URL的编码问题,确保路径准确性。

  3. 保证Core Data操作的线程安全
    更新PlayListModel和保存modelContext的操作放到主线程执行(如果modelContext是主线程上下文):

    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }
        lista.haveFile = true
        lista.playlistUrl = fileURL // 若改为URL类型
        do {
            self.modelContext.insert(lista)
            try self.modelContext.save()
        } catch {
            print("保存PlayListModel失败: \(error)")
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:54:52