1

I'm using Realm in my iOS app and because of the thread safety issues I have opted to create a realm layer for my data model, as well as a layer that can be converted to and from the realm objects.

There is a lot more complexity to what I'm actually doing, but I was able to create a playground that demonstrates the unexpected issues I'm running into.

Basically I created a protocol called RealmObject. In my actual app this is a protocol which Realm.Object types can conform to which requires all the necessary properties.

I'm just including the UUID part of this protocol in the playground.

//the realm layer
protocol RealmObject {
    var uuid: String { get }
}

Next I have my Realm layer model. They all have UUID, but have some other properties that correspond to their non-Realm relatives.

struct NoteObject: RealmObject {
    let uuid: String
    let noteText: String
}
struct AppointmentObject: RealmObject {
    let uuid: String
    let time: Date
}

Cacheable is the protocol that types which can be saved in realm must conform to. For now I've just included the UUID requirement, and the requirement to be initialized from a RealmObject

//Cacheable
protocol Cacheable {
    associatedtype T: RealmObject
    init(object: T)
}
struct Note: Cacheable {

    let noteText: String
    let uuid: String
    init(object: NoteObject) {
        self.uuid = object.uuid
        self.noteText = object.noteText
    }
}
struct Appointment: Cacheable {
    let uuid: String
    let time: Date
    init(object: AppointmentObject) {
        self.uuid = object.uuid
        self.time = object.time
    }

}

Finally the issue. In my app this function does a lot more than this, but this is as simple as I could make it and still demonstrate the issue. Basically, T is the generic type which must be a Cacheable object. U is the RealmObject I want to convert to a cacheable object.

Every Cacheable object has an initializer that accepts an object which is a RealmObject

But it fails

func getCacheable<T: Cacheable, U: RealmObject>(from realmObject: U) -> T {

    let thing = T(object: realmObject)
    ERROR: Non-nominal type 'T' does not support explicit initialization
    return thing
}

let noteObject = NoteObject(uuid: "bobalobla", noteText: "hi how are you")
let note: Note = getCacheable(from: noteObject)

let appointmentObject = AppointmentObject(uuid: "bobloblaw", time: Date())
let appointment: Appointment = getCacheable(from: appointmentObject)

I don't see anything that is so ambiguous the compiler shouldn't be able to easily figure it out

Replacing the generics with the types should be simple

Once the function knows which Cacheable type its working with, the initializer should be easily route to the correct init method. I won't pretend to understand whats actually happening, but this seems like a pretty basic thing to do with generics so I assume I must be doing something wrong. What is going on?

YichenBman
  • 5,011
  • 8
  • 46
  • 65
  • I may be totally wrong here, which is why I'm commenting rather than answering, but does replacing the `T(object: realmObject)` with `T.init(object: realmObject)` have an effect? – Connor Neville Apr 11 '18 at 19:19
  • Possible duplicate of https://stackoverflow.com/questions/46458657/non-nominal-type-x-does-not-support-explicit-initialization – dr_barto Apr 11 '18 at 19:21
  • @Connor Cannot invoke 'init' with an argument list of type '(object: U)' is the error thats thrown. It should be worth noting that I also attempted passing T.Type in as a function parameter, so basically `type: T.Type` and then `type(object: realmObject)` but that throws the exact same error in my title – YichenBman Apr 11 '18 at 19:22
  • 2
    `U` is not necessarily `T.T` – you need to pass a `T.T` to `T`'s `init(object:)` (i.e replace the `U` parameter of your function with `T.T`). – Hamish Apr 11 '18 at 19:24

1 Answers1

2

The type system doesn't know, from your list of generics <T: Cacheable, U: RealmObject>, that U is the correct type of RealmObject for T (in other words, you could be passing a Note as T, and an AppointmentObject as U). You just need to update your function signature to:

// The `where T.T == U` is the important part. Because that is defined,
// we can also remove `U: RealmObject` because it's redundant information.
func getCacheable<T: Cacheable, U>(from realmObject: U) -> T where T.T == U {
    let thing = T(object: realmObject)
    return thing
}

// Alternatively, you can just do:
func getCacheable<T: Cacheable>(from realmObject: T.T) -> T {
    let thing = T(object: realmObject)
    return thing
}
Connor Neville
  • 7,291
  • 4
  • 28
  • 44