如何将通用自定义对象保存到 UserDefaults?
How to save a generic custom object to UserDefaults?
这是我的通用 class:
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
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 class 就像 SMState<T: Hashable>
,你可以在这个 SMState Class.
中发送任何种类的枚举类型
然后你可以使用这个 SMState Class 作为你想要的没有通用
enum A_ENUM_KEY {
case KEY_1
case KEY_2
}
let stateEncodeData = NSKeyedArchiver.archivedData(withRootObject: currentState)
UserDefaults.standard.set(stateEncodeData, forKey: "state")
本例中currentState是SMState类型,SMState.value是SomeEnum,因为Any Enums是AnyHashable
如果想让泛型class采用NSCoding
,泛型T
要编解码那么T
必须是 属性 列表兼容类型之一。
属性 列表兼容类型为 NSString
、NSNumber
、NSDate
和 NSData
一个可能的解决方案是创建一个协议 PropertyListable
并将 属性 列表兼容类型的所有 Swift 等价物扩展到该协议
协议要求是
- 一个
associated type
.
- 计算 属性
propertyListRepresentation
将值转换为 属性 列表兼容类型。
- 初始化程序
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)!
}
}
最后用
替换通用 class
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
这样的自定义类型,则实现会更容易(并且更短)。
要解决 "NSInvalidArgumentException', reason: : unrecognized selector sent to instance",请确保您尝试归档的 class 的超级 class 也扩展了 NSCoder。
这是我的通用 class:
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
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 class 就像 SMState<T: Hashable>
,你可以在这个 SMState Class.
然后你可以使用这个 SMState Class 作为你想要的没有通用
enum A_ENUM_KEY {
case KEY_1
case KEY_2
}
let stateEncodeData = NSKeyedArchiver.archivedData(withRootObject: currentState)
UserDefaults.standard.set(stateEncodeData, forKey: "state")
本例中currentState是SMState类型,SMState.value是SomeEnum,因为Any Enums是AnyHashable
如果想让泛型class采用NSCoding
,泛型T
要编解码那么T
必须是 属性 列表兼容类型之一。
属性 列表兼容类型为 NSString
、NSNumber
、NSDate
和 NSData
一个可能的解决方案是创建一个协议 PropertyListable
并将 属性 列表兼容类型的所有 Swift 等价物扩展到该协议
协议要求是
- 一个
associated type
. - 计算 属性
propertyListRepresentation
将值转换为 属性 列表兼容类型。 - 初始化程序
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)!
}
}
最后用
替换通用 classopen 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
这样的自定义类型,则实现会更容易(并且更短)。
要解决 "NSInvalidArgumentException', reason: : unrecognized selector sent to instance",请确保您尝试归档的 class 的超级 class 也扩展了 NSCoder。