ios - How to save a generic custom object to UserDefaults? -
this generic 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") } }
then want this:
let stateencodedata = nskeyedarchiver.archiveddata(withrootobject: currentstate) userdefaults.standard.set(stateencodedata, forkey: "state")
in case currentstate
of type smstate<someenum>.
but when call nskeyedarchiver.archiveddata
, xcode (9 beta 5) shows message in purple saying:
attempting archive generic swift class 'stepup.smstate<stepup.routineviewcontroller.routinestate>' mangled runtime name '_ttgc6stepup7smstateocs_21routineviewcontroller12routinestate_'. runtime names generic classes unstable , may change in future, leading non-decodable data.
i not sure tries say. not possible save generic object ?
is there other way save generic custom object ?
edit
:
even if use anyhashable
instead of generics same error on runtime when calling nskeyedarchiver.archiveddata
:
terminating app due uncaught exception 'nsinvalidargumentexception', reason: : unrecognized selector sent instance
if want make generic class adopt nscoding
, generic type t
going encoded , decoded t
must 1 of property list compliant types.
property list compliant types nsstring
, nsnumber
, nsdate
, nsdata
a possible solution create protocol propertylistable
, extend swift equivalents of property list compliant types protocol
the protocol requirements
- an
associated type
. - a computed property
propertylistrepresentation
convert value property list compliant type. - an initializer
init(propertylist
contrary.
public protocol propertylistable { associatedtype propertylisttype var propertylistrepresentation : propertylisttype { } init(propertylist : propertylisttype) }
here exemplary implementations 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) } }
lets declare sample enum , adopt 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)! } }
finally replace generic 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") } }
with implementation can create instance , archive it
let currentstate = smstate<foo>(foo.north) let stateencodedata = nskeyedarchiver.archiveddata(withrootobject: currentstate)
and unarchive again
let restoredstate = nskeyedunarchiver.unarchiveobject(with: stateencodedata) as! smstate<foo> print(restoredstate.value)
the whole solution seems cumbersome have fulfill restriction nscoding
requires property list compliant types. if don't need custom type enum
implementation easier (and shorter).
Comments
Post a Comment