Swift 5开发Mac命令行工具:解析Plist嵌套字典时函数返回nil的问题排查
Swift 5解析Plist时
getCustomPrefs返回nil的问题 你遇到的核心问题是你的Swift结构体和Plist的实际结构完全不匹配,导致PropertyListDecoder无法完成解码,所以getCustomPrefs一直返回nil。我们一步步拆解问题和解决方案:
问题分析
先看你的Plist结构:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>TestValues</key> <dict> <!-- 这里是单个嵌套字典,不是数组 --> <key>TestString</key> <string>This is my test string value</string> <key>TestInt</key> <integer>4</integer> <key>AnotherTestString</key> <string>This is my other test string value</string> <key>TestBool</key> <true/> </dict> </dict> </plist>
而你的Swift代码里,CustomPrefs把TestValues定义成了数组类型[TestValuePrefs],但Plist里TestValues是一个单个字典,不是数组。同时TestValuePrefs的结构是试图包含这个字典里的所有键,但解码时因为类型不匹配,直接失败返回nil。
另外,你的需求是把TestValues里的每一组键值对放进数组,当前的结构体设计也没有满足这个目标。
解决方案
方案1:先修正结构体解码,再转换为键值对数组
首先调整结构体匹配Plist的实际结构,让解码能成功,之后再把字典内容转换成你需要的数组:
// 匹配Plist结构的顶层结构体 struct CustomPrefs: Codable { var TestValues: [String: AnyCodable] // 用AnyCodable兼容不同类型的值 } // 处理多类型值的辅助结构体 struct AnyCodable: Codable { let value: Any init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let string = try? container.decode(String.self) { value = string } else if let int = try? container.decode(Int.self) { value = int } else if let bool = try? container.decode(Bool.self) { value = bool } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "不支持的类型") } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch value { case let string as String: try container.encode(string) case let int as Int: try container.encode(int) case let bool as Bool: try container.encode(bool) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: container.codingPath, debugDescription: "不支持的类型")) } } } func getCustomPrefs() -> [(key: String, value: Any)]? { let decoder = PropertyListDecoder() let url = URL(fileURLWithPath: "/Library/Preferences/com.test.preferences.plist") guard let data = try? Data(contentsOf: url), let prefs = try? decoder.decode(CustomPrefs.self, from: data) else { print("解码Plist失败") return nil } // 将字典转换为键值对数组 return prefs.TestValues.map { (key: $0.key, value: $0.value.value) } } // 调用并打印结果 if let preferenceArray = getCustomPrefs() { for item in preferenceArray { print("Key: \(item.key), Value: \(item.value)") } } else { print("未找到偏好设置") }
方案2:直接解码为字典类型(更简单)
如果不需要强类型的结构体,也可以直接把Plist解码成字典类型,然后提取TestValues并转换为数组:
func getCustomPrefs() -> [(key: String, value: Any)]? { let url = URL(fileURLWithPath: "/Library/Preferences/com.test.preferences.plist") guard let plistDict = try? NSDictionary(contentsOf: url) as? [String: [String: Any]], let testValues = plistDict["TestValues"] else { print("读取或解析Plist失败") return nil } // 转换为键值对数组 return testValues.map { (key: $0.key, value: $0.value) } } // 调用并打印结果 if let preferenceArray = getCustomPrefs() { for item in preferenceArray { print("Key: \(item.key), Value: \(item.value)") } } else { print("未找到偏好设置") }
关键说明
- 用
PropertyListDecoder和Codable时,必须保证结构体的层级、类型完全匹配Plist的结构,否则解码会静默失败返回nil。 - 因为
TestValues里的值类型多样(字符串、整数、布尔值),所以需要用AnyCodable或者直接用字典类型来处理,不能用固定类型的结构体数组来接收。
内容的提问来源于stack exchange,提问作者BrianBadonde
相关产品推荐
相关产品推荐

