在 UserDefaults 中存储 class 对象时,枚举编码值为 nil。 Codable Protocol 已经被继承

enum encoded value is nil while storing the class object in UserDefaults. Codable Protocol is already inherited

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

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

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
    }
}

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

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

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

            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))")
            }

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

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

它打印 nil。

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

检索它

您应该在 CodingKeys 枚举中包含 userType

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

或者完全删除 CodingKeys 枚举,因为默认情况下,所有属性都作为编码键包含在内。 CodingKeys 枚举中的键决定了合成的 Codable 实现将编码和解码的内容。如果不包含 userTypeuserType 将不会被编码,因此它不会存储到 UserDefaults.

I am not getting it from Server and userType is an external property outside the JSON response

这很好,因为 userType 是可选的。如果 JSON 没有密钥,它将被分配 nil。这可能是一个问题,如果你也在编码 User 并将其发送到服务器, 服务器无法处理请求中的额外键,在这种情况下你需要两个结构 - 一个用于存储来自 UserDefaults 的 to/loading,一个用于 parsing/encoding 服务器 response/request.

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

观察

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

以下实施解决了所有这些问题。

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 }
}