ios - 如何将通用自定义对象保存到 UserDefaults?

标签 ios swift nscoding

这是我的通用类:

open class SMState<T: Hashable>: NSObject, NSCoding {
    open var value: T

    open var didEnter: ( (_ state: SMState<T>) -> Void)?
    open var didExit:  ( (_ state: SMState<T>) -> Void)?

    public init(_ value: T) {
        self.value = value
    }

    convenience required public init(coder decoder: NSCoder) {
        let value = decoder.decodeObject(forKey: "value") as! T

        self.init(value)
    }

    public func encode(with aCoder: NSCoder) {
        aCoder.encode(value, forKey: "value")
    }
}

然后我想这样做:

    let stateEncodeData = NSKeyedArchiver.archivedData(withRootObject: currentState)
    UserDefaults.standard.set(stateEncodeData, forKey: "state")

以我为例 currentState类型为 SMState<SomeEnum>.

但是当我调用 NSKeyedArchiver.archivedData , Xcode (9 beta 5) 显示一条紫色消息:

Attempting to archive generic Swift class 'StepUp.SMState<StepUp.RoutineViewController.RoutineState>' with mangled runtime name '_TtGC6StepUp7SMStateOCS_21RoutineViewController12RoutineState_'. Runtime names for generic classes are unstable and may change in the future, leading to non-decodable data.

我不太确定它想说什么。无法保存通用对象?

还有其他方法可以保存通用自定义对象吗?

edit :

即使我使用 AnyHashable在调用 NSKeyedArchiver.archivedData 时,我在运行时遇到了相同的错误,而不是泛型:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: : unrecognized selector sent to instance

最佳答案

如果想让泛型类采用NSCoding,泛型类型T要编解码,那么T 必须是符合属性列表的类型之一

符合属性列表的类型有 NSStringNSNumberNSDateNSData


一个可能的解决方案是创建一个协议(protocol) PropertyListable 并将属性列表兼容类型的所有 Swift 等价物扩展到该协议(protocol)

协议(protocol)要求是

  • 关联类型
  • 计算属性 propertyListRepresentation,用于将值转换为符合属性列表的类型。
  • 初始化器 init(propertyList 做相反的事情。

public protocol PropertyListable {
    associatedtype PropertyListType
    var propertyListRepresentation : PropertyListType { get }
    init(propertyList : PropertyListType)
}

以下是 StringInt 的示例性实现。

extension String : PropertyListable {
    public typealias PropertyListType = String
    public var propertyListRepresentation : PropertyListType { return self }
    public init(propertyList: PropertyListType) { self.init(stringLiteral: propertyList) }
}

extension Int : PropertyListable {
    public typealias PropertyListType = Int
    public var propertyListRepresentation : PropertyListType { return self }
    public init(propertyList: PropertyListType) { self.init(propertyList) }
}

让我们声明一个示例枚举并采用 PropertyListable

enum Foo : Int, PropertyListable {
    public typealias PropertyListType = Int

    case north, east, south, west

    public var propertyListRepresentation : PropertyListType { return self.rawValue }
    public init(propertyList: PropertyListType) {
        self.init(rawValue:  propertyList)!
    }
}

最后用

替换你的泛型类
open class SMState<T: PropertyListable>: NSObject, NSCoding {
    open var value: T

    open var didEnter: ( (_ state: SMState<T>) -> Void)?
    open var didExit:  ( (_ state: SMState<T>) -> Void)?

    public init(_ value: T) {
        self.value = value
    }

    convenience required public init(coder decoder: NSCoder) {
        let value = decoder.decodeObject(forKey: "value") as! T.PropertyListType
        self.init(T(propertyList: value))
    }

    public func encode(with aCoder: NSCoder) {
        aCoder.encode(value.propertyListRepresentation, forKey: "value")
    }
}

通过此实现,您可以创建一个实例并将其归档

let currentState = SMState<Foo>(Foo.north)
let stateEncodeData = NSKeyedArchiver.archivedData(withRootObject: currentState)

再次解压

let restoredState = NSKeyedUnarchiver.unarchiveObject(with: stateEncodeData) as! SMState<Foo>
print(restoredState.value)

整个解决方案似乎很麻烦,但您必须满足 NSCoding 需要属性列表兼容类型的限制。如果您不需要像 enum 这样的自定义类型,则实现会更容易(也更短)。

关于ios - 如何将通用自定义对象保存到 UserDefaults?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45751848/

相关文章:

ios - DSLCalendarView 重新加载

ios - Swift Snapchat fetchSnapUserInfo() 'UserEntity' 值不是已解析的身份

swift - 只能通过使用最终类来满足的 Swift 协议(protocol)要求

ios - 如何在 iOS/Swift 中存储并保存更新的问题和答案集

swift - 为什么我的 SKTileMapNode 类型的对象没有被解码?

ios - 如何通过索引属性从核心数据中获取对象?

ios - 如何计算有机形状的面积?

ios - 在 for 循环中运行多个 URLRequest,先完成后执行

ios - Swift 中的 NSCoding 没有正确归档数据?

ios - TableView : how to get a string from selected row?