Class + generic case issue for Swift

class BaseCellModel {}
class BaseCell<Model: BaseCellModel>: NSObject {
    var model: Model? { nil }
}

class DerivedCellModel: BaseCellModel {}
class DerivedCell: BaseCell<DerivedCellModel> {}

let cell: NSObject = DerivedCell()
// Challenge: Cast cell to BaseCell so that we can access cell.model

let r1 = cell as? BaseCell // ❌
let r2 = cell as? BaseCell<BaseCellModel> // ❌

let r3 = cell as? BaseCell<DerivedCellModel> // ✅
let r4 = cell as? DerivedCell // ✅

Is there a way to access cell.model without knowing the concrete Derived* type as there may have many subclass type?

1 Like

The solution by now is adding another layer to solve it.

protocol P {
    associatedtype Model: BaseCellModel
    var model: Model? { get }
}

extension BaseCell: P {}

Then we can use the following code to access model without knowing the Derived*

let r5 = cell as? any P
r5?.model

It's a little awkward. But at least it works for me.

AFAIK, Swift does not support covariance or contra-variance regarding to generic parameters. (There are exceptions in the standard library which are specially handled, like Array)

That is to say, commonly speaking, given a generic type G<T>, if A is a subtype of B, then currently there's no relation between G<A> and G<B> in Swift.

In your case, I would model a type-erased base class so that most of the operations can be defined on it. (Of course it can be defined as a protocol, just like the way you did, but I believe some type-erasing would be required eventually)

class AbstraceCell {
  var abstractModel: BaseCellModel { get }
}

class BaseCell<Model: BaseCellModel>: AbstraceCell {
  var model: Model { get }
}

class DerivedCell: BaseCell<DerivedCellModel> { }
2 Likes

Thanks. End up doing this.

class AbstractBaseCell: NSObject {
    var baseModel: BaseCellModel?
}

class BaseCellModel {}
class BaseCell<Model: BaseCellModel>: AbstractBaseCell {
    var model: Model? {
        get { baseModel as? Model }
        set { baseModel = newValue }
    }
}

class DerivedCellModel: BaseCellModel {}
class DerivedCell: BaseCell<DerivedCellModel> {}

let r1 = cell as? BaseCell // ❌
let r2 = cell as? BaseCell<BaseCellModel> // ❌

let r3 = cell as? BaseCell<DerivedCellModel> // ✅
let r4 = cell as? DerivedCell // ✅

let r5 = cell as? AbstractBaseCell // ✅
r5?.baseModel