问题
tl;dr Can you generically instantiate an object using a conforming protocol's initializer method while also still preserving the object's intended type? What I am trying now is seg faulting the compiler.
In a library I am writing, I am trying to accomplish a goal of generically serializing/deserializing objects using Swift's limited introspection features.
Here is the code for a global function that sets a variable's value using reflection. It attempts to reconcile for nested constructs if it finds a dictionary:
func model__setValue<T where T: NSObject, T: Serializable>(value: AnyObject, forSerializationKey key: String, model m: T) {
let varNames = object__getVarNames(mirror: reflect(m)) // Gets a list of this object's variable names
if let i = find(m.serializationKeys, key) {
if value is [String : AnyObject] {
// This allows us to have nested dictionary representations
// of Serializable constructs and have them init properly
let t1 = reflect(m)[i].1.valueType as NSObject.Type
if t1 is Serializable.Type {
let t2 = t1 as Serializable.Type
let finalObj = t2(dictionary: value as [String : AnyObject]) // Segmentation fault: 11
m.setValue(finalObj, forKey: varNames[i])
}
} else {
m.setValue(value, forKey: varNames[i])
}
}
}
Couple of things to explain:
Serializable
is a protocol defining methods including init(dictionary). It is adopted by objects that want to be (de)serialized.Serializable
also specifies a computed property of "serialization keys" or a list of strings that are used as the dictionary keys for the object's variables and must be a one-to-one mapping of variable names to serialization keys. Why do this? Sometimes, API calls return keys that don't really make sense anymore (schema rot?), or I just want to name a variable differently, as I don't tend to enjoy underscores too much.- The model has to both be an
NSObject
and conform toSerializable
. Why? To achieve generics like this, I needed the ability to write to an object's fields without know their identifiers ahead of time. Swift doesn't have a way to do this natively, so subclassing NSObject is a good compromise. Why not have a root object with those features anyway?
My goal with this is stupid easy, low overhead construction of objects from their JSON representation (even with different keys and variable names, even with nested objects). So give an object it's list of keys and make the protocol's init
call another generic constructor function which iterates over the object's variables and calls this each time. Bam. That's all. Now you can parse all the things from all the (JSON) APIs and write extremely little or no parse logic. I wanted the same to apply to a serializer too, such that toDictionary
also requires just one generic method call.
In this function, we are setting an object's value dynamically using reflection. First, we make sure this is a valid key to set on the object. Next, we determine whether or not the value we want to set is a dictionary. If it is, I want to grab the target object's type to see if it is in fact also a Serializable
conforming type. If that field is, we can use this dictionary to recursively construct it (this doesn't work as explained shortly). If it isn't, or the value isn't a dictionary at all, just use Cocoa's setValue:forKey: method.
Alas, I can't get the nested object part to work quite right, and I haven't figured out if what I am trying to do is just impossible or if I am just doing it wrong. I'm also concerned my let finalObj
line isn't valid, despite Xcode's lack of error on it. My thought process is just call Serializable
's init
constructor and go on my merry way. I assume the issue with this is Swift's statically typed nature and the compiler not actually knowing what final type the init will return (although, Xcode highlight's t2
in that line in the same color as it does keywords...). I am not really sure if there's a way to say t1 is an NSObject
that also conforms to Serializable
. I think what I am attempting now with t2
and the cast is losing type information and thus causing the seg fault, though again Xcode doesn't complain about it until I actually go to build.
I've tried several ways of reconciling this nested Serializable
thing and haven't been able to come up with a solution. Would anyone be able to assist?
回答1:
Check out this article, somebody else did something just like what you are trying. It might help you, or it might actually be just what you are looking for: https://www.weheartswift.com/swift-objc-magic/
来源:https://stackoverflow.com/questions/27194384/swift-generic-object-json-serialization