Search code examples
swiftgenericsprotocols

How to make a dynamic generic?


I'm trying to write a protocol that allows me to version models in my app. In order to do that I wrote the following VersionManager.

class VersionManager<Type: Decodable> {
    private var database: Database

    init(database: Database) {
        self.database = database
    }

    var versions: [Type] {
        return []
    }
}

After that I wrote a protocol that I can add to models:

protocol Versionable {

}

extension Versionable {
    private var manager: VersionManager<Restaurant> {
        return VersionManager<Restaurant>(database: Database.shared)
    }

    public var versions: [Restaurant] {
        return manager.versions
    }
}

Now, the problem I'm facing is that I tried passing the type dynamically instead of hardcoded, like I have now for Restaurant.

So I tried changing the protocol to this:

protocol Versionable {
    var kind: Decodable.Type { get }
}

Then I wanted to pass kind to VersionManager. However, when I try that Xcode throws this error: Expected '>' to complete generic argument list.

Is there any other way to do this?


Solution

  • If you want to use generics inside a protocol, you need to use an associatedtype

    protocol Versionable {
        associatedtype Model: Decodable
    }
    
    extension Versionable {
        private var manager: VersionManager<Model> {
            return VersionManager<Model>(database: Database.shared)
        }
    
        public var versions: [Model] {
            return manager.versions
        }
    }
    

    The model that is going to implement the Versionable protocol will have to resolve this type:

    struct SomeModel: Versionable {
        typealias Model = Int
    }
    
    SomeModel().versions // [Int]
    

    I'm guessing Restaurant in your example refers to the model that implements Versionable. In that case, you can just use the Self reference inside your protocol extension:

    protocol Versionable: Decodable {
    }
    
    extension Versionable {
        private var manager: VersionManager<Self> {
            return VersionManager<Self>(database: Database.shared)
        }
    
        public var versions: [Self] {
            return manager.versions
        }
    }
    
    struct SomeModel: Versionable {}
    SomeModel().versions // [SomeModel]
    

    Please note that the Versionable protocol now requires the Decodable conformance because of the VersionManager<Type: Decodable> generic constraint.