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

这是我的通用类:

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的types是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而不是generics,在调用NSKeyedArchiver.archivedData时,在运行时会得到相同的错误:

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

如果要使generics类采用NSCoding ,并且genericstypesT将要被编码和解码,那么T 必须是属性列表兼容types之一

属性列表兼容types是NSStringNSNumberNSDateNSData


一个可能的解决scheme是创build一个协议PropertyListable ,并将属性列表兼容types的所有Swift等价物扩展到该协议

协议要求是

  • 一个associated type
  • 计算属性propertyListRepresentation将该值转换为属性列表兼容types。
  • 初始化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)! } } 

最后用你的replace你的generics类

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

有了这个实现,你可以创build一个实例并存档

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

并再次取消归档

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

整个解决scheme似乎是麻烦的,但你必须履行NSCoding要求属性列表兼容types的限制。 如果你不需要象enum这样的自定义types,那么实现就简单得多了。

 open class SMState: NSObject, NSCoding { open var value: AnyHashable open var didEnter: ( (_ state: SMState) -> Void)? open var didExit: ( (_ state: SMState) -> Void)? public init(_ value: AnyHashable) { self.value = value } convenience required public init(coder decoder: NSCoder) { let value = decoder.decodeObject(forKey: "value") as! AnyHashable self.init(value) } public func encode(with aCoder: NSCoder) { aCoder.encode(value, forKey: "value") } } 

现在这个SMState类就像SMState<T: Hashable> ,你可以在这个SMState类中发送任何种类的枚举types。

那么你可以使用这个SMState类作为你想要没有generics

 enum A_ENUM_KEY { case KEY_1 case KEY_2 } let stateEncodeData = NSKeyedArchiver.archivedData(withRootObject: currentState) UserDefaults.standard.set(stateEncodeData, forKey: "state") 

在这种情况下,currentState是SMStatetypes,并且SMState.value是SomeEnum,因为任何枚举都是AnyHashable

要解决“NSInvalidArgumentException”,原因:无法识别的select器发送到实例“,请确保您要存档的类的超类也扩展NSCoder。