Reputation: 2291
Hy, I'm trying to build a base class that does certain actions that repeat themselves along the app, but I'm faced with an issue that I can't really comprehend, the following code exemplifies what I'm trying to build:
protocol GenericSection { }
protocol GenericRow { }
protocol GenericModel {
var section: GenericSection { get }
var items: [GenericRow] { get }
}
protocol GenericVM {
var model: [GenericModel] { get set }
}
class ExampleVM: GenericVM {
enum Row: GenericRow {
case aRow
}
enum Section: GenericSection {
case aSection
}
struct Model: GenericModel {
let section: Section
let items: [Row]
}
var model: [Model] = []
}
This does not compile because Model
does not conform to GenericModel
and ExampleVM
does not conform to GenericVM
.
The way it solves this is by using GenericRow
, GenericSection
and GenericModel
, my question is why can't I use the respective Row
, Section
and Model
that conform to those protocols.
Upvotes: 0
Views: 727
Reputation: 54706
The issue is that when you declare a concrete type requirement in a protocol definition, you cannot use a subclass (or type conforming to the protocol if the declared type requirement is a protocol type), you need to use the actual (protocol) type in the conforming class.
This is the behaviour you see for inheritance. You cannot override an inherited property by making the type of the property a subclass of the type declared in the superclass. This would result in unexpected behaviour when using the superclass type/protocol type to represent subclasses/conforming classes.
You can get around this issue by making your protocol generic using associated types.
protocol GenericSection { }
protocol GenericRow { }
protocol GenericModel {
associatedtype Section:GenericSection
associatedtype Row: GenericRow
var section: Section { get }
var items: [Row] { get }
}
protocol GenericVM {
associatedtype Model: GenericModel
var model: [Model] { get set }
}
class ExampleVM: GenericVM {
enum Row: GenericRow {
case aRow
}
enum Section: GenericSection {
case aSection
}
struct Model: GenericModel {
let section: Section
let items: [Row]
}
var model: [Model] = []
}
Upvotes: 2
Reputation: 28892
Based on your protocols, this would be the correct way to make the classes and structs you created conform:
protocol GenericSection { }
protocol GenericRow { }
protocol GenericModel {
var section: GenericSection { get }
var items: [GenericRow] { get }
}
protocol GenericVM {
var model: [GenericModel] { get set }
}
class ExampleVM: GenericVM {
var model: [GenericModel] = []
enum Row: GenericRow {
case aRow
}
enum Section: GenericSection {
case aSection
}
struct Model: GenericModel {
var section: GenericSection
var items: [GenericRow]
}
}
Upvotes: 1
Reputation: 6600
You declared your GenericModel
/GenericVM
using protocol types per se, not types implementing your GenericSection
/GenericRow
protocols.
To make your code work you gonna need to use associatedtype
s:
protocol GenericSection { }
protocol GenericRow { }
protocol GenericModel {
associatedtype SectionType: GenericSection
associatedtype RowType: GenericRow
var section: SectionType { get }
var items: [RowType] { get }
}
protocol GenericVM {
associatedtype ModelType: GenericModel
var model: [ModelType] { get set }
}
class ExampleVM: GenericVM {
enum Row: GenericRow {
case aRow
}
enum Section: GenericSection {
case aSection
}
struct Model: GenericModel {
let section: Section
let items: [Row]
}
var model: [Model] = []
}
And now it works, and knows everything about your concrete types:
let vm = ExampleVM()
vm.model = [.init(section: .aSection, items: [.aRow])]
Upvotes: 1