wvteijlingen / Spine

A Swift library for working with JSON:API APIs. It supports mapping to custom model classes, fetching, advanced querying, linking and persisting.
MIT License
266 stars 109 forks source link

Encode/decode fields #55

Closed bonroyage closed 8 years ago

bonroyage commented 8 years ago

I ran into this problem where I wanted to store a record in NSData so I could temporarily cache it and share among targets (ie. the app and a today extension) but none of the fields were populated anymore.

So I added an iterator to loop over the fields to the encoder/decoder of the resource and store/load those values.

wvteijlingen commented 8 years ago

Thanks for your PR Roy! I've thought about adding such functionality multiple times before. Every time I decide not to do so though. I think the base Resource class should be responsible for decoding/encoding its own properties only. It's up to the developer to decide and implement the coding for subclasses. Perhaps not every field should be encoded, or perhaps they should be encoding in a different way. This is pretty much how it's done all throughout the Cocoa frameworks, you need to implement your own coding, and call super in both methods.

I appreciate your work but I don't think I'll merge it at this point. If you want this behaviour you can subclass Resource and then subclass that into concrete resource classes.

bonroyage commented 8 years ago

That's a fair reasoning. I'll subclass Resource then :) Thanks!

bonroyage commented 8 years ago

I seem to be having problems subclassing resource.

I created a new file, lets say MyResource.swift with the following contents:

import Foundation
import Spine

public class MyResource : Resource {

    public required init(coder: NSCoder) {
        super.init(coder: coder)

        for field in fields {
            if coder.decodeObjectForKey(field.name) != nil { // here
                setValue(coder.decodeObjectForKey(field.name), forField: field.name) // here
            }
        }
    }

    override public func encodeWithCoder(coder: NSCoder) {
        super.encodeWithCoder(coder)

        for field in fields {
            coder.encodeObject(valueForField(field.name), forKey: field.name) // here
        }
    }
}

However it complains that Value of type 'Field' has no member 'name' while following fields clearly reveals it uses the Field class, which does have the property name, this exact same code worked while it was in your Resource class.

Any ideas what I'm missing here?

wvteijlingen commented 8 years ago

The name variable is internal to the Spine module, so it's not visible in your application code. You can use the serializedName variable.

Edit: No, I now see that you can't actually because serializedName is internal too. Hmm, might need to make that public.

bonroyage commented 8 years ago

Okay, making that public solved one issue. But in my own class I had to "override" the init(coder) function (without actually using the word override) but now it expects me to add the coder parameter to all places where I call my resource (ie. Event)

spine.registerResource(Event.resourceType) { Event() }

throws Missing argument for parameter 'coder' in call

bonroyage commented 8 years ago

I managed to fix the above issue by overriding the regular init

public override init() {
    super.init()
}

Instead of making serializedName public, please make name public because name is the one that is used to store the data in the model whereas serializedName is the one used in the JSON response and doesn't necessarily correspond to the key in the model.

wvteijlingen commented 8 years ago

I'm thinking about getting rid of the name variable as a whole. It don't that that it is an implicitly unwrapped optional to support fieldsFromDictionary. I think changing Resource.fields to [String: Field], getting rid of fieldsFromDictionary is the way to go.