如何将通用自定义对象保存到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是NSString
, NSNumber
, NSDate
和NSData
一个可能的解决scheme是创build一个协议PropertyListable
,并将属性列表兼容types的所有Swift等价物扩展到该协议
协议要求是
- 一个
associated type
。 - 计算属性
propertyListRepresentation
将该值转换为属性列表兼容types。 - 初始化
init(propertyList
做相反的事情。
public protocol PropertyListable { associatedtype PropertyListType var propertyListRepresentation : PropertyListType { get } init(propertyList : PropertyListType) }
以下是String
和Int
示例性实现。
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。