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

时间:2017-08-18 08:17:12

标签: 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

3 个答案:

答案 0 :(得分:3)

如果您想使通用类采用NSCoding并且通用类型T将被编码和解码,那么T 必须是符合属性列表之一类型

符合财产清单的类型包括NSStringNSNumberNSDateNSData

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

协议要求是

  • associated type
  • 计算属性 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这样的自定义类型,则实施会更容易(也更短)。

答案 1 :(得分:0)

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类中发送任何类型的枚举类型。

然后你可以使用这个SMState类,而不需要Generic

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,因为任何枚举都是 AnyHashable

答案 2 :(得分:0)

要解决&#34; NSInvalidArgumentException&#39;,原因::无法识别的选择器发送到实例&#34;,请确保您尝试存档的类的超类也扩展NSCoder。