evermeer / EVReflection

Reflection based (Dictionary, CKRecord, NSManagedObject, Realm, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift
Other
965 stars 119 forks source link

Could not create an instance for type #285

Closed eliezer-ferra closed 6 years ago

eliezer-ferra commented 6 years ago

Hi I have two clases (Customer & Device) that extend EVObject. The Customer class has a property of type Device.

open class Device: EVObject {
    open var id: String
    open var name: String // e.g. "My iPhone"
    open var model: String // e.g. @"iPhone", @"iPod touch"
    open var systemName: String // e.g. @"iOS"
    open var systemVersion: String // e.g. @"4.0"

    public required convenience init() {
        self.init(id: "", name: "", model: "", systemName: "", systemVersion: "")
    }

    public init (id:String, name: String, model: String, systemName: String, systemVersion: String) {
        self.id = id
        self.name = name
        self.model = model
        self.systemName = systemName
        self.systemVersion = systemVersion
    }
}
open class Customer: EVObject {
    open var id: String
    open var name: String
    open var email: String
    open var phone: String
    open var activationCode: String
    open var isActive: Bool
    open var devices: [Device]
    open var validationDate: String?

    public required convenience init() {
        self.init(name: "", email: "", phone: "", activationCode: "")
    }

    public init(name: String, email:String, phone: String, activationCode: String) {
        id = ""
        self.name = name
        self.email = email
        self.phone = phone
        self.activationCode = activationCode
        isActive = true
        devices = []
    }
}

I'm converting the Customer object to JSON String and later that JSON String converting it back to the Customer object.

extension UserDefaults {
    open func customer(forKey defaultName: String) -> Customer? {
        if let jsonData = string(forKey: defaultName) {
            print(jsonData)
            return Customer(json: jsonData)
        } else {
            return nil
        }
    }

    open func set(_ value: Customer, forKey defaultName: String) {
        print(value.toJsonString())
        set(value.toJsonString(), forKey: defaultName)
    }
}

When initializing the Customer object from the JSON string, all the properties from the object are loaded except the devices property. The library throwing this error "Could not create an instance for type Device".

evermeer commented 6 years ago

Are you using EVReflection in a framework? Are the class definitions in another bundle than the main bundle? If so, then you have to make EVReflection aware that it should also look for class definitions in another bundle. You could do that by calling the following function once at startup: EVReflection.setBundleIdentifier(Device.self) I added a test for your issue and the serialization / deserialization works ok: https://github.com/evermeer/EVReflection/blob/master/UnitTests/EVReflectionTests/EVReflectionIssue285.swift#L29

eliezer-ferra commented 6 years ago

Yes, I was using it in a framework. I made the suggested chance and it worked.

Now I have a question: if both classes were in the framework and not in the main bundle, how come the Customer class was loaded successfully but the Device wasn't?

evermeer commented 6 years ago

Good question... The moment you do the: return Customer(json: jsonData) It uses the initializer defined in EVObject. So you are using standard code for initializing that class. It does not need to be created.

Your Customer object has an array of Device object. On initialization that array does exist, but the Device objects that goes in that array do not exist. There is no generic Swift code for creating instances for an array.

With the Mirror command I only get that it's of type Swift.Array From that II can get the sting 'Device' and I need to create an instance. For that I am using the NSClassFromString. If that string does not have the bundle specified, then it will look in the main bundle. When you use the setBundleIdentifier, EVReflection will also try to instantiate that class by adding that bundle identifier in front of it.

I do have a planned update where I keep a reference to the parent object and pass it on to the createClass function so that it could get the bundle from the parent object and use that. But for now you have to use the setBundleIdentifier.

eliezer-ferra commented 6 years ago

Got it! Thank you so much for you help. Great framework btw.