Swift本地JSON文件更新后原文件未变更,求助原因
问题背景
你在Swift项目的Data文件夹中创建了data.json作为初始数据,成功实现了从Bundle读取JSON并赋值到全局变量,但当更新全局变量后调用saveJSON方法,项目中的data.json始终没有变化,即使修改保存文件名也无效。
初始JSON内容:
{ "fullname": "Testuser2", "answered": 0, "correct": 0 }
核心代码包含UserData结构体、全局变量定义、JSON读取方法及保存方法。
问题原因
1. Bundle内文件只读,保存路径并非项目目录
你读取的是Bundle.main中的data.json,但iOS APP的主Bundle是只读目录,安装后无法修改其中的文件。而你的saveJSON方法是把数据保存到了设备的Documents目录,并非项目中的Data文件夹,所以Xcode里看到的项目文件自然不会有变化。
2. 保存的JSON结构与原文件不一致
你将UserData包装成数组[UserData]进行编码,但原JSON是单个对象结构,编码后的JSON为数组格式,后续读取会触发解码错误,且不符合初始数据结构。
3. 读取逻辑未适配保存路径
你的readLocalFile始终从Bundle读取文件,即使成功保存到Documents目录,下次读取还是会加载Bundle里的初始数据,修改后的数据无法被复用。
解决步骤及修正代码
1. 修正保存逻辑:保存单个对象,输出错误信息
修改saveJSON方法,直接编码单个UserData对象,添加错误打印和保存路径输出:
func saveJSON() { print("---------") print(GlobalVariable.fullname) print(GlobalVariable.answered) print(GlobalVariable.correct) print("---------") // 直接使用单个UserData对象,而非数组 let dataToUpdate = UserData(fullname: GlobalVariable.fullname, answered: GlobalVariable.answered, correct: GlobalVariable.correct) do { let jsonEncoder = JSONEncoder() jsonEncoder.outputFormatting = .prettyPrinted // 格式化JSON,方便查看 let jsonData = try jsonEncoder.encode(dataToUpdate) guard let jsonString = String(data: jsonData, encoding: .utf8) else { print("Failed to convert JSON data to string") return } print(jsonString) guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { print("Failed to access document directory") return } let savePath = documentDirectory.appendingPathComponent("data.json") try jsonString.write(to: savePath, atomically: true, encoding: .utf8) print("Data saved successfully to: \(savePath)") } catch { print("Save error: \(error.localizedDescription)") } }
2. 修正读取逻辑:优先读取Documents目录的文件
修改readLocalFile方法,先尝试读取Documents目录中保存的文件,若不存在再加载Bundle里的初始数据:
private func readLocalFile(forName name: String) -> Data? { // 优先读取Documents目录的文件 let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let docFileURL = documentDir.appendingPathComponent("\(name).json") if FileManager.default.fileExists(atPath: docFileURL.path) { do { return try Data(contentsOf: docFileURL) } catch { print("Error reading from documents: \(error)") } } // 若Documents无文件,读取Bundle中的初始数据 do { guard let bundlePath = Bundle.main.path(forResource: name, ofType: "json"), let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) else { return nil } return jsonData } catch { print("Error reading from bundle: \(error)") } return nil }
3. 优化建议:避免使用全局变量
全局变量不利于数据维护和状态管理,建议改用单例类管理用户数据:
class UserDataManager { static let shared = UserDataManager() private(set) var userData: UserData? private init() {} func loadUserData() { guard let data = readLocalFile(forName: "data") else { return } do { userData = try JSONDecoder().decode(UserData.self, from: data) } catch { print("Decode error: \(error)") } } func updateUserData(answered: Int, correct: Int) { guard let currentData = userData else { return } // 若要直接修改属性,需将UserData的属性改为var userData = UserData(fullname: currentData.fullname, answered: answered, correct: correct) saveUserData() } private func saveUserData() { guard let data = userData else { return } do { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let jsonData = try encoder.encode(data) guard let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let savePath = docDir.appendingPathComponent("data.json") try jsonData.write(to: savePath) } catch { print("Save error: \(error)") } } // 复用readLocalFile方法 private func readLocalFile(forName name: String) -> Data? { // 同之前的实现 } }
验证保存结果
你可以通过Xcode的「Devices and Simulators」查看模拟器的Documents目录,验证数据是否正确保存:
- 打开Xcode,点击菜单「Window」→「Devices and Simulators」
- 选择对应的模拟器和APP,点击「Download Container」
- 右键下载的容器文件,选择「Show Package Contents」,进入「AppData/Documents」即可看到保存的文件
内容的提问来源于stack exchange,提问作者Dragounow

