swiftswift-protocols

How to specify a specific protocol in an array of protocols


I have a protocol, which has a property that is another protocol. I have a few structs that implement that protocol and I'm happy with them. However, I would like to have an array of the protocol, but specify that the inner protocol is always a specific type.

It is probably easier if I show some example code:

protocol MyCategory {
    var displayTitle: String {get}
}

enum Category1: MyCategory {
    case person
    case place
    case thing
    
    var displayTitle: String {
        switch self {
           ...
        }
    }
}
enum Category2: MyCategory {
    case animal
    case vegetable
    case mineral
    
    var displayTitle: String {
        switch self {
           ...
        }
    }
}

protocol FieldModel {
    var category: MyCategory { get }
    var name: String { get }
}

struct IntFieldModel: FieldModel {
    let category: MyCategory
    let name: String
    let value: Int
}

struct StringFieldModel: FieldModel {
    let category: MyCategory
    let name: String
    let value: String
}

struct Category1ConsolidatedResults {
    let fields: [FieldModel]
}

I would like to be able to tell Category1ConsolidatedResults that every item in fields will be of type FieldModel but also that every category in that every FieldModel will be of type Category1.

I tried to do something with generics and associatedtype but I couldn't figure out how to pass the type into the FieldModel protocol in Category1ConsolidatedResults. Instead I got the error "Use of protocol 'FieldModel' as a type must be written 'any FieldModel'".

Is there a way to pass a type to a generic protocol?


Solution

  • You should indeed add an associated type to FieldModel, so that category information is encoded into the type system. In addition to that, you can make this the primary associated type of FieldModel, so that in Category1ConsolidatedResults, you can write [any FieldModel<Category1>].

    protocol FieldModel<Category> {
        associatedtype Category: MyCategory
        var category: Category { get }
        var name: String { get }
    }
    
    struct IntFieldModel<Category: MyCategory>: FieldModel {
        let category: Category
        let name: String
        let value: Int
    }
    
    struct StringFieldModel<Category: MyCategory>: FieldModel {
        let category: Category
        let name: String
        let value: String
    }
    
    struct Category1ConsolidatedResults {
        let fields: [any FieldModel<Category1>]
    }
    
    struct Category1ConsolidatedResults {
        let fields: [any FieldModel<Category1>]
    }
    

    Also consider writing a ConsolidatedResults type, generic over the category type:

    struct ConsolidatedResults<Category: MyCategory> {
        let fields: [any FieldModel<Category>]
    }