realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

Swift model class types can't be generic

Open tengyifei opened this issue 8 years ago • 8 comments

Goals

Use generics in Realm Models.

class Foo<T>: Object {
    required init() {
        super.init()
    }
}
class Bar: Object {
    let baz = List<Foo<String>>()
}

Expected Results

Initializing Realm database works.

Actual Results

fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=0 "Schema validation failed due to the following errors:
- Target type '_TtGC5Bar3FooSS_' doesn't exist for property 'baz'."

If I replace let baz = List<Foo<String>>() with let baz = Foo<String>(), the app runs but the property is missing from the database since it is not dynamic. dynamic var baz = List<Foo<String>>() does not work either as generics are not supported in Objective-C.

Steps to Reproduce

Run an app with the above model definitions.

Version of Realm and Tooling

ProductName:    Mac OS X
ProductVersion: 10.11.4
BuildVersion:   15E65

/Applications/Xcode.app/Contents/Developer
Xcode 7.3.1
Build version 7D1014

/usr/local/bin/pod
1.0.0
Realm (0.103.1)
RealmSwift (0.103.1)

/usr/local/bin/bash
GNU bash, version 4.3.42(1)-release (x86_64-apple-darwin15.0.0)

/usr/local/bin/carthage
0.16.2
(not in use here)

/usr/local/bin/git
git version 2.8.2

tengyifei avatar May 22 '16 05:05 tengyifei

I wonder if using generics is ever possible in Realm. I'm pretty new to Objective-C and only recently discovered that generic classes cannot be marked dynamic. Does this imply they will never by discovered by Realm runtime? How about List, RealmOptional and LinkingObjects?

tengyifei avatar May 25 '16 21:05 tengyifei

Hello @tengyifei,

Unfortunately, you are right - generic classes inheriting from Objective-C base classes cannot be fully represented in Objective-C, and so it's not possible to do what you want. The generic Realm Swift collection classes are thin wrappers around their Objective-C equivalents, and do not run into this limitation (as they do not need to be introspected).

Please feel free to reach out if you have any further questions.

austinzheng avatar May 25 '16 22:05 austinzheng

Would it be possible to extend Realm such that we can use List with a generic type parameter, by creating non-generic counterparts through substitution at runtime? Maybe the new Swift reflection API can help?

tengyifei avatar May 26 '16 17:05 tengyifei

A few thoughts on this:

class Foo<T>: Object {}

Here Foo isn't just one type, it's an "archetype": think of it like an abstract type that can't be used without further specialization. In other words, it's potentially a different type for each T that satisfies the generic requirements, and in this case it's unconstrained. Of course, you wouldn't want Realm to create an unconstrained number of tables in the underlying database, so how would you expect this to work?

We can't just ignore the generic and treat all Foo<T> as Foo since that would mean that Realm.objects(Foo) would return different types, breaking the homogenous container concept of Results (or SequenceType for that matter).

jpsim avatar May 26 '16 17:05 jpsim

You're right, Foo<T> could be different every time. What I'm proposing is not accommodating arbitrary type parameters, but translating Foo<T> into a non-generic type without erasing the parameter (think of C++ name mangling that preserves the type parameters).

tengyifei avatar May 26 '16 17:05 tengyifei

I think I understand what you want. You'd be able to define:

class Foo<T> : Object { /* */ }

var a : Foo<Int>
var b : Foo<String>

and this would be transformed into something like:

class Foo_generic_Int : Object { /* */ }
class Foo_generic_String : Object { /* */ }

var a : Foo_generic_Int
var b : Foo_generic_String

Unfortunately, this isn't possible right now in Swift as far as I can tell. If Swift gets a macro system in the future, it might be worth revisiting this feature.

austinzheng avatar May 26 '16 18:05 austinzheng

I certainly understand that, but it doesn't change anything in my last response.

Here's something we could technically do, but it'd be a ton of work and IMO likely not worth the investment:

Allow Object to be subclassed into generic types and require that all specializations be passed in to Realm.Configuration.objectTypes when creating the Realm.

Despite what @austinzheng just wrote, I think Swift's reflection support would be enough to support this, but I'm not 100% sure.

jpsim avatar May 26 '16 18:05 jpsim

Yes. Well I agree that doing the transformation is the job of the compiler, but maybe we can leverage the reflection support in Swift to inform the type to Objective-C.

The reason I asked this is because I was writing an automatic wrapper transforming arbitrary types into Objects.

import RealmSwift

postfix operator % { }

protocol RealmCustomEncodable {
    associatedtype Serialized
    associatedtype Raw
    static func encode(input: Raw) -> Serialized
    static func decode(input: Serialized) -> Raw
}

class CustomEncoded<S: RealmCustomEncodable> : Object {
    var val: S.Serialized   // this is what's actually stored in Realm
    init(_ initial: S.Raw) {
        val = S.encode(initial)
        super.init()
    }
    func read() -> S.Raw {
        return S.decode(val)
    }
    func set(newval: S.Raw) -> Void {
        val = S.encode(newval)
    }
}

// operator that let's us read the wrapped value
postfix func % <T> (input: CustomEncoded<T>) -> T.Raw {
    return input.read()
}

// assigning to the wrapped value
func &= <T> (left: CustomEncoded<T>, right: T.Raw) -> Void {
    left.set(right)
}

Then to store an NSLocale for example, we would write

extension NSLocale : RealmCustomEncodable {
    static func encode(input: NSLocale) -> String {
        return input.localeIdentifier
    }

    static func decode(input: String) -> NSLocale {
        return NSLocale(localeIdentifier: input)
    }
}

And use it as let currencyLocale: CustomEncoded<NSLocale> in any Realm object.

Then discovered that generics are not supported in Realm :[

Allowing for generics would essentially permit the user to store any type in Realm without boilerplate code.

tengyifei avatar May 26 '16 18:05 tengyifei