簡體   English   中英

如何將通用自定義對象保存到UserDefaults?

[英]How to save a generic custom object to 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的類型為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.archivedDataAnyHashable在運行時遇到相同的錯誤:

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

如果要使泛型類采用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那樣的自定義類型,則實現更容易(也更短)。

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,因為Any Enums是AnyHashable

要解決“NSInvalidArgumentException”,原因::發送到實例的無法識別的選擇器“,請確保您嘗試存檔的類的超類也擴展了NSCoder。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM