Search code examples
iosswiftgenericsswift-protocolsopaque-types

Swift Generics/Opaque Types: trying to use protocol conformance to convert between types in a generic function


I'm building a Converter to convert my DataObjects to ModelObjects and vice versa. I currently have two functions to do the converting. One for each conversion.

Currently, both of my conversion functions do exactly the same thing with exceptions to the type it takes and the type it returns. I was hoping I could write a generic function that would be able to handle both conversions because, otherwise, the code is exactly the same.

Here's what I've got: Obviously these are simplified examples

I've built a Protocol that contains the requirements for properties and an init(). I've implemented this Protocol and its init() in both the DataObject and ModelObject.

My Protocol:

protocol MyProtocol {
    var id: UUID { get }
    var name: String? { get }

    init(id: UUID, name: String?)
}

My DataObject:

class DataObject: Object, MyProtocol {
    @Persisted(primaryKey: true) var id: UUID

    @Persisted var name: String?

    required convenience init(id: UUID, name: String?) {
        self.init()
        self.id = id
        self.name = name
    }
}

My ModelObject:

struct ModelObject: MyProtocol {
    var id: UUID
    var name: String?

    init(id: UUID, name: String?) {
        self.id = id
        self.name = name
    }
}

Currently: This is an example what is currently doing the conversions. I have one like this for each conversion.

static func buildModelObject(object: DataObject) -> ModelObject {
    let returnObject = ModelObject(id: object.id, name: object.name)
    return returnObject
}

Desired Converter Function: I hope to accomplish something like this.

static func buildObject<O: MyProtocol, R: MyProtocol>(objectIn: O, returnType: R.Type) -> R {
    let returnObject = returnType.init(id: objectIn.id, name: objectIn.name)
    return returnObject
}

And I'm calling it like this:

let object = Converter.buildObject(objectIn: dataObject, returnType: ModelObject.self)

Obviously, this isn't working. Error: "Protocol 'MyProtocol' as a type cannot conform to the protocol itself". The error is on the function call. This is the only error XCode is showing me at compile.

I'm not certain what to do here or how to get it working. I'm not even confident that this error message is even helpful in any way.

The Question:

Is it even possible to accomplish something like this? If so, what am I missing? I'm still very unfamiliar with Swift Generics.

I don't have an issue with using multiple conversion functions as it's still better than getting the entire code base bogged down with Realm specific stuff. But, given that these functions are essentially the same, I'm hoping that Generics might be able to help reduce otherwise similar code into a single function.

Note: The DataObject is a Realm Object. I understand they have cool features like live/managed Objects. We're trying to keep our Persistence layer completely separated from the rest of the app with a single API/Entry Point. This is why I'm using these Converters.


Solution

  • You almost had it

    func buildObject<R: MyProtocol>(objectIn: MyProtocol, returnType: R.Type) -> R {
        let returnObject = returnType.init(id: objectIn.id, name: objectIn.name)
        return returnObject
    }
    

    the key observation: at no point do we need the concrete type of objectIn, so just require the protocol type.