【问题标题】:enum encoded value is nil while storing the class object in UserDefaults. Codable Protocol is already inherited将类对象存储在 UserDefaults 中时,枚举编码值为 nil。可编码协议已被继承
【发布时间】:2021-06-17 08:10:04
【问题描述】:

我是 iOS 新手,正在尝试将用户对象存储在 UserDefaults 中。这样当应用再次启动时,我可以检查用户类型并根据它导航到相关屏幕。

为此,我创建了一个如下的用户类(可编码),它有一个 userType 枚举属性!

enum UserType: Int, Codable {
    case userType1 = 0 
    case userType2 = 1
    case notDetermined = 2
    
    init(from decoder: Decoder) throws {
        let label = try decoder.singleValueContainer().decode(Int.self)
        self = UserType(rawValue: label) ?? .notDetermined
    }
}

class User: Codable {
    public var userFullName: String? = ""
    public var userType: UserType? //= .notDetermined
    
    enum CodingKeys: String, CodingKey {
        case userFullName
    }
}

在我的视图控制器类中,我正在为用户对象创建一个新实例并尝试存储用户默认值,如下所示:

        let newUser = User()
        newUser.userFullName = "Test"
        newUser.userType = userTypeBtn.isSelected ? .userType1 : .userType2
        

当我打印 newUser 的 userType 时,无论选择哪个,我都能看到正确的值。但在那之后,当我尝试将其存储在 userDefaults 中时,它会为 userType 属性返回 nil。

            do {
                let encoded = try JSONEncoder().encode(newValue)
                UserDefaults.standard.set(encoded, forKey: UserDefaultKey.currentUser)
                UserDefaults.standard.sync()
            } catch {
                print("Unable to Encode User Object: (\(error))")
            }

当我尝试打印这个 encoded 变量并在控制台中对其进行解码时

JSONDecoder().decode(User.self, from: encoded).userType

它打印 nil。

请帮助我如何在 UserDefaults 中存储可选枚举属性并在需要时使用 Codable 检索它

【问题讨论】:

  • 为什么在CodingKeys 枚举中只包含userFullName
  • @Sweeper 因为我没有从服务器获取它,并且 userType 是 JSON 响应之外的外部属性!之前我在 CodingKeys 中也添加了 userType,但问题仍然存在!
  • 添加它应该不会破坏您的 JSON 解析代码,不是吗?保存到UserDefaults 不起作用的原因是因为您没有将userType 作为编码键。
  • “但问题仍然存在!”
  • @Sweeper 我从模拟器中删除了该应用程序并重新启动它。我对 CodingKey 的想法是,如果我们希望定义自己的属性名称而不是从服务器接收到的名称,我们可以将服务器键映射到我们自己定义的键。这就是我所相信的!

标签: swift codable


【解决方案1】:

您应该在 CodingKeys 枚举中包含 userType

enum CodingKeys: String, CodingKey {
    case userFullName
    case userType
}

或者完全删除CodingKeys 枚举,因为默认情况下,所有属性都包含为编码键。 CodingKeys 枚举中的键决定了合成的 Codable 实现将编码和解码什么。如果不包含userType,则userType不会被编码,所以不会存储到UserDefaults中。

我没有从服务器获取它,并且 userType 是 JSON 响应之外的外部属性

这很好,因为userType 是可选的。如果 JSON 没有密钥,它将被分配nil。如果您还编码 User 并将其发送到服务器,这可能是一个问题,并且服务器无法处理请求中的额外键,在这种情况下,您需要两个结构 - 一个用于存储到/从UserDefaults 加载,一个用于解析/编码服务器响应/请求。

请记住在尝试时将新的User 编码为UserDefaults,因为旧的仍然没有userType 编码。

【讨论】:

    【解决方案2】:

    观察

    1. Decodable 的一部分enum UserType: Int, Codable 自定义实现可能不是最好的主意。 Swift 编译器支持开箱即用的编码/解码enum X: Int,无需您为其编写自定义实现。 (事实上​​,从 Swift 5.5 开始,Swift 编译器现在可以对具有关联值的 case 的枚举执行此操作。)
    2. 您应该尽量避免出现.notDetermined 之类的情况。要么用户具有明确定义的类型,要么 user.type 是nil。您可以轻松地在用户本身上定义便捷的 getter 以了解其类型。
    3. Swift 允许嵌套类型,因此在 Swift 中使用 User.Kind 而不是 UserType 更自然。

    以下实施会处理所有这些问题。

    import Foundation
    
    class User: Codable {
        enum Kind: Int, Codable {
            case free = 1
            case pro = 2
        }
        public var fullName: String?
        public var kind: Kind?
    }
    
    let newUser = User()
    newUser.fullName = "Test"
    newUser.kind = .free
    
    do {
        let encoded = try JSONEncoder().encode(newUser)
        UserDefaults.standard.set(encoded, forKey: "appUser")
        
        if let fetched = UserDefaults.standard.value(forKey: "appUser") as? Data {
            let decoded = try JSONDecoder().decode(User.self, from: fetched)
            print(decoded)
        }
    }
    

    以上代码包括定义、构造、encodeAndStore、fetchAndDecode,无需任何自定义实现即可满足您的所有需求。


    奖金

    上面的代码没有为User 打印一个很好的描述。为此,您可以像这样添加CustomStringConvertible 一致性。

    extension User: CustomStringConvertible {
        var description: String {
            """
            fullName: \(fullName ?? "")
            kind: \(kind?.description ?? "")
            """
        }
    }
    
    extension User.Kind: CustomStringConvertible {
        var description: String {
            switch self {
            case .free: return "free"
            case .pro: return "pro"
            }
        }
    }
    

    如果您在实现此功能后尝试print(decoded),您将清楚地看到您想为User 实例看到的内容。


    User.kind 可以是 nil,我不想每次需要在应用程序的不同屏幕上检查时都用 if let 处理它。

    不用担心,可以简化成这样。

    extension User {
        var isFreeUser: Bool { kind == .free }
        var isProUser: Bool { kind == .pro }
    }
    

    【讨论】:

    • 感谢分享对 CustomStringConvertible 协议的深刻理解,以及如何为我的自定义类嵌套枚举。真的很感激!
    • 在答案中,您可以看到enum Kind 嵌套在class User 中。看看我们如何写extension User.Kind
    • 是的,我对此进行了审查并仅以这种方式修改了我当前的集成。感谢您的大力帮助并创建了一个清晰的课程!
    猜你喜欢
    • 1970-01-01
    • 2018-05-14
    • 2018-10-16
    • 2020-11-11
    • 2018-11-26
    • 2018-06-22
    • 1970-01-01
    相关资源
    最近更新 更多