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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 13:57:34