Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Swift enums #921

Closed
aschuch opened this issue Sep 19, 2014 · 22 comments
Closed

Support Swift enums #921

aschuch opened this issue Sep 19, 2014 · 22 comments

Comments

@aschuch
Copy link

aschuch commented Sep 19, 2014

Swift adds great new features to enums.

However, it is not possible to directly persist enums to a Realm, because of the libraries use of the dynamic keyword (Obj-C only).
At the moment I am solving such issues like the following, but this should really be handled by the RLMObject behind the scenes.

Open for other suggestions ✨

enum State: String {
    case Open = "open"
    case Closed = "closed"
}

class Foo: RLMObject {
    dynamic var stateRaw = ""
    var state: State {
        get {
            if let a = State.fromRaw(stateRaw) {
                return a
            }
            return .Closed
        }
    }
}
@jpsim
Copy link
Contributor

jpsim commented Sep 21, 2014

Your work-around is the best way to accomplish this for the time being. There are several implications we need to consider before adding official support for Swift enum's, especially concerning migrations.

In any case, thanks for the feature request!

@jpsim jpsim changed the title Support enums Support Swift enums Nov 12, 2014
@alazier alazier added the backlog label Dec 5, 2014
@segiddins segiddins added the swift label Apr 1, 2015
@gabro
Copy link

gabro commented Jun 18, 2015

This would be really nice to have, especially when dealing with JSON data.

In the meanwhile the snippet by @aschuch (thank you!) can be a little shorter using ?? and dropping the get:

var state: State {
  return State(rawValue: stateRaw) ?? .Closed
}

@danyalaytekin
Copy link

Is this still the case now that we have RealmSwift?

@segiddins
Copy link
Contributor

@danyalaytekin unfortunately, this is still the case, as Realm Swift still relies on properties being dynamic.

@danyalaytekin
Copy link

@segiddins np, thanks for the quick confirmation! 👍

@frazer-rbsn
Copy link

Would love to see this implemented! Thanks guys!

@segiddins
Copy link
Contributor

@frazer-rbsn this is still impossible, even as of Swift 2.0, as the runtime doesn't give us the information we need to be able to determine what we need to store under the hood.

@frazer-rbsn
Copy link

@segiddins
Thanks for looking into this. The workaround posted here is simple and works a treat anyway. Thanks!

@tom-sparo
Copy link
Contributor

For anyone who comes across this in the future -- KVC is a little bit trickier with using the raws if you don't want external users to have to worry about remembering which fields are backed by raws. So I've found a good way is to override the setValue(:value :undefinedKey) and valueForUndefinedKey methods to make these computed properties KVC compliant.

@marchy
Copy link

marchy commented Mar 23, 2016

Any progress/updates on this guys? This is really a core use case for domain modeling, would be super useful to have this supported first-hand (or at least a section about the current best-practice workaround in the docs). Pretty please.

@marchy
Copy link

marchy commented Apr 5, 2016

@segiddins In terms of runtime not giving you enough info, what do you mean by this? Could you not require provide a simple protocol that users would adopt on their enums to give you whatever it is that's needed (ie: CustomStringConvertible for example if you need to map the enum to a string to persist). Or your own RealmEnumValueConvertible in case the user already implemented CustomStringConvertible and wants to differ the persisted value from it (rare, but could be valid in certain cases).

PS: I find the deltas between the model objects and what's needed to persist them in Realm is really blowing up our domain - I think it would be a great goal for the team to try to prioritize minimizing these differences for the overall adoption/practicality of the framework. Enum persistence would be a great win on this front.

@piv199
Copy link

piv199 commented Jul 22, 2016

Is there any changes as for Swift 3.0?

@jpsim
Copy link
Contributor

jpsim commented Jul 22, 2016

Yes, lots. But none regarding Swift enums and Realm.

@marchy
Copy link

marchy commented Jul 23, 2016

@jpsim I think what @piv199 meant was whether Swift 3.0 added any of the runtime info that @segiddins mentioned eg: "...as of Swift 2.0, as the runtime doesn't give us the information we need to be able to determine what we need to store under the hood".

Also, what do you guys think of my proposed solution to provide a protocol that enums can adopt in order to provide you with the serialized info you need?

@jpsim
Copy link
Contributor

jpsim commented Jul 23, 2016

Yes, I understood that, but should have been clearer in my response. There are no changes in Swift 3 that would allow us to do this.

Also, what do you guys think of my proposed solution to provide a protocol that enums can adopt in order to provide you with the serialized info you need?

This would have to be done more comprehensively, allowing the decomposition of all types into Realm primitives in order to keep Realm files cross-platform, but yes this is something that should be done.

@HT154
Copy link

HT154 commented Oct 3, 2016

It's possible to declare scalar-type-backed enums as @objc. Properties with declared @objc enum types can de marked dynamic, so wouldn't this enable realm to access the values? I've used this approach with Core Data in the past:

import CoreData
class Thread: NSManagedObject {
    @NSManaged var state: State // @NSManaged == dynamic
    @objc enum State: Int64 {
        case ready
        case running
        case waiting
    }
}

EDIT: Actually, this already works, as far as I can tell.

import RealmSwift
class Thread: Object {
    dynamic var state: State = .ready
    @objc enum State: Int {
        case ready
        case running
        case waiting
    }
}

It would be awesome to get RealmOptional support for these enums too, since that still seems impossible.

@AbelToy
Copy link

AbelToy commented Aug 16, 2018

Would native enums be easier to support with the changes introduced in Swift 4.2, or is there anything else required?

@bjarkehs
Copy link

It was mention in #2375 that work is being done on supporting types that implement RawRepresentable. I understand that RawRepresentable types cannot be used as properties, and thus you would still need to do the juggling with a raw value and then a computed property.

Recently I've started using the Tagged library. So I do something along the lines of:

final class Todo: Object {
    enum TodoIdTag {}
    typealias Identifier = Tagged<TodoIdTag, String>

    @objc dynamic var rawId: Identifier.RawValue = UUID().uuidString
    
    var id: Identifier {
        get {
            return Identifier(rawValue: self.rawId)
        }
        set {
            self.rawId = newValue.rawValue
        }
    }
}

However this gets really confusing when finding a single object using object(ofType:forPrimaryKey:), since I'd like to use the computed id property. Tagged extends RawRepresentable so I can just use id.rawValue, but if I forget my app crashes in a fiery ball of death.

It would be great if func dynamicBridgeCast<T>(fromSwift x: T) -> Any could allow RawRepresentable types. Basically this could be done by checking if it's a RawRepresentable type and then instead of passing the RawRepresentable type just pass the rawValue.

Maybe something like:

extension RawRepresentable: CustomObjectiveCBridgeable where RawValue: CustomObjectiveCBridgeable {
    static func bridging(objCValue: Any) -> Int64 {
        return objCValue as! RawValue
    }
    var objCValue: Any {
        return self.rawValue
    }
}

@tgoyne
Copy link
Member

tgoyne commented Apr 30, 2019

The big limiting factor is that the property getter/setters which actually read from and write to the Realm file are implemented via the objective-c runtime, which means that they have to be types which obj-c supports. RealmOptional and List circumvent this by doing fairly complicated things that unfortunately involve a hardcoded list of the supported types on the obj-c side, so it wouldn't be totally straightforward to generalize it to a type which lets you pick a storage type, a mapped type, and a conversion function.

SE-0258 probably will make it possible to at least make the wrapper property approach a lot simpler and work better, but as always the fact that it doesn't really interop with obj-c may limit how good the end result can be.

@marchy
Copy link

marchy commented Jun 10, 2020

@tgoyne Two questions on this front:

  1. It's 2020. Can you comment on the internal plan on cutting out Obj-C altogether from Realm? It seems the hold-back on this has gone a good 5 years or so... it's probably time to move on, just as Apple has with the introduction of Combine + other big iOS 13 SDK changes. Being held back by Obj-C or support for it in any way seems quite crazy at this point.

  2. How have Swift 5.1's property wrappers changed the equation on this? Can we use them to support raw backing fields with the existing framework in a nicer way?

  3. How is the recent Combine support play into all this? If we wanted to observe these fields via publishers, is there the equivalent of @Published that we could use (ie: @RealmPublished) instead of adding PassThroughSubjects into the mix of all this mapping?

Thanks for your honest thoughts on all this.

@tgoyne
Copy link
Member

tgoyne commented Jun 10, 2020

Supporting obj-c isn't limiting what we can do in Swift. We're perfectly willing to ship Swift-only features, and splitting the obj-c and swift SDKs into entirely separate SDKs is on the table if there's ever a solid reason to do so (although it's worth noting that if we did, the obj-c SDK would have a large enough user base to continue maintaining it). We continue to do things via the obj-c runtime because Swift lacks the language features needed to make things work without a compile-time code generation step, and without compiler support to make that painless (as Java and .NET have) we don't consider that a viable path.

I put together a prototype of property wrapper based model definitions a year ago. It doesn't dramatically change what we can support unfortunately; it'd let us support things like Int? without the RealmOptional kludge but wouldn't be enough to make things like enums with associated values possible. I have some time set aside for the next quarter to try to turn that into something shippable, but it depends somewhat on how long dealing with the new things announced at WWDC takes.

I haven't had a chance yet to look into single-property publishers to see if there's anything useful we could do there.

@tgoyne
Copy link
Member

tgoyne commented Mar 28, 2022

The @Persisted feature supports all RawRepresentable Swift enums.

@tgoyne tgoyne closed this as completed Mar 28, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests