realm-swift
realm-swift copied to clipboard
Swift model class types can't be generic
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
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
?
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.
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?
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).
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).
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.
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.
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 Object
s.
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.