Protocol can only be used as a generic constraint because it has Self or associatedType requirements
Suppose for the moment we adjust your protocol to add a routine that uses the associated type:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
And Swift were to let you create an array of RequestType
the way you want to. I could pass an array of those request types into a function:
func handleQueueOfRequests(queue: [RequestType]) {
// frobulate All The Things!
for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}
I get down to the point that I want to frobulate all the things, but I need to know what type of argument to pass into the call. Some of my RequestType
entities could take a LegoModel
, some could take a PlasticModel
, and others could take a PeanutButterAndPeepsModel
. Swift is not happy with the ambiguity so it will not let you declare a variable of a protocol that has an associated type.
At the same time it makes perfect sense to, for example, create an array of RequestType
when we KNOW that all of them use the LegoModel
. This seems reasonable, and it is, but you need some way to express that.
One way to do that is to create a class (or struct, or enum) that associates a real type with the abstract Model type name:
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
Now it's entirely reasonable to declare an array of LegoRequestType
because if we wanted to frobulate
all of them we know we would have to pass in a LegoModel
each time.
This nuance with Associated Types makes any protocol that uses them special. The Swift Standard Library has Protocols like this most notably Collection
or Sequence
.
To allow you to create an array of things that implement the Collection
protocol or a set of things that implement the sequence protocol, the Standard Library employs a technique called "type-erasure" to create the struct types AnyCollection<T>
or AnySequence<T>
. The type-erasure technique is rather complex to explain in a Stack Overflow answer, but if you search the web there are lots of articles about it.
I can recommend a video from Alex Gallagher on Protocols With Associated Types (PATs) on YouTube.
From Swift 5.1 - Xcode 11
You can use an opaque result type to achieve something like that.
imagine this:
protocol ProtocolA {
associatedtype number
}
class ClassA: ProtocolA {
typealias number = Double
}
So the following generates the error:
var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */
But making the type opaque by adding the some
keyword before the type will fix the issue and usually thats the only thing we want:
var objectA: some ProtocolA = ClassA()
Swift 5.1
An example how you can use generic protocols by implementing an associated type and base protocol:
import Foundation
protocol SelectOptionDataModelProtocolBase: class{}
protocol SelectOptionDataModelProtocol: SelectOptionDataModelProtocolBase {
associatedtype T
var options: Array<T> { get }
var selectedIndex: Int { get set }
}
class SelectOptionDataModel<A>: SelectOptionDataModelProtocol {
typealias T = A
var options: Array<T>
var selectedIndex: Int
init(selectedIndex _selectedIndex: Int, options _options: Array<T>) {
self.options = _options
self.selectedIndex = _selectedIndex
}
}
And an example View Controller:
import UIKit
struct Car {
var name: String?
var speed: Int?
}
class SelectOptionViewController: UIViewController {
// MARK: - IB Outlets
// MARK: - Properties
var dataModel1: SelectOptionDataModelProtocolBase?
var dataModel2: SelectOptionDataModelProtocolBase?
var dataModel3: SelectOptionDataModelProtocolBase?
// MARK: - Initialisation
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init() {
self.init(title: "Settings ViewController")
}
init(title _title: String) {
super.init(nibName: nil, bundle: nil)
self.title = _title
self.dataModel1 = SelectOptionDataModel<String>(selectedIndex: 0, options: ["option 1", "option 2", "option 3"])
self.dataModel2 = SelectOptionDataModel<Int>(selectedIndex: 0, options: [1, 2, 3])
self.dataModel3 = SelectOptionDataModel<Car>(selectedIndex: 0, options: [Car(name: "BMW", speed: 90), Car(name: "Toyota", speed: 60), Car(name: "Subaru", speed: 120)])
}
// MARK: - IB Actions
// MARK: - View Life Cycle
}