Protocol can only be used as a generic constraint because it has Self or associatedType requirements

IosSwiftGenericsSwift2Swift Protocols

Ios Problem Overview


I have a protocol RequestType and it has associatedType Model as below.

public protocol RequestType: class {

    associatedtype Model
    var path: String { get set }
       
}

public extension RequestType {
    
    public func executeRequest(completionHandler: Result<Model, NSError> -> Void) {
        request.response(rootKeyPath: rootKeyPath) { [weak self] (response: Response<Model, NSError>) -> Void in
            completionHandler(response.result)
            guard let weakSelf = self else { return }
            if weakSelf.logging { debugPrint(response) }
        }
    }
    
}

Now I am trying to make a queue of all failed requests.

public class RequestEventuallyQueue {
    
    static let requestEventuallyQueue = RequestEventuallyQueue()
    let queue = [RequestType]()
    
}

But I get the error on line let queue = [RequestType]() that Protocol RequestType can only be used as a generic constraint because it has Self or associatedType requirements.

Ios Solutions


Solution 1 - Ios

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.

Solution 2 - Ios

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()

Solution 3 - Ios

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

    
}

Solution 4 - Ios

A little change in design of your code could make it possible. Add an empty, non-associatedType, protocol at the top of your protocol hierarchy. Like this...

public protocol RequestTypeBase: class{}

public protocol RequestType: RequestTypeBase {

    associatedtype Model
    var path: Model? { get set } //Make it type of Model

}
public class RequestEventuallyQueue {

    static let requestEventuallyQueue = RequestEventuallyQueue()
    var queue = [RequestTypeBase]() //This has to be 'var' not 'let'

}

Another example, with classes derived from the protocol RequestType, making a queue and passing the queue to a function to print appropriate type

public class RequestA<AType>: RequestType{
   public typealias Model = AType
   public var path: AType?
}
public class RequestB<BType>: RequestType{
   public typealias Model = BType
   public var path: BType?
}

var queue = [RequestTypeBase]()

let aRequest: RequestA = RequestA<String>()
aRequest.path = "xyz://pathA"

queue.append(aRequest)

let bRequest: RequestB = RequestB<String>()
bRequest.path = "xyz://pathB"

queue.append(bRequest)

let bURLRequest: RequestB = RequestB<URL>()
bURLRequest.path = URL(string: "xyz://bURLPath")

queue.append(bURLRequest)

func showFailed(requests: [RequestTypeBase]){

    for request in requests{
        if let request = request as? RequestA<String>{
            print(request.path!)
        }else if let request = request as? RequestB<String>{
            print(request.path!)
        }else if let request = request as? RequestB<URL>{
            print(request.path!)
        }
    
    }
}

showFailed(requests: queue)

Solution 5 - Ios

This error may also occur in the following scenario:

protocol MyProtocol {
    assosciatedtype SomeClass
    func myFunc() -> SomeClass
}

struct MyStuct {
    var myVar = MyProtocol
}

In this case, all you have to do to fix the issue is to use generics:

protocol MyProtocol {
    assosciatedtype SomeClass
    func myFunc() -> SomeClass
}

struct MyStuct<T: MyProtocol> {
    var myVar = T
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRahul KatariyaView Question on Stackoverflow
Solution 1 - IosScott ThompsonView Answer on Stackoverflow
Solution 2 - IosMojtaba HosseiniView Answer on Stackoverflow
Solution 3 - IosTimBigDevView Answer on Stackoverflow
Solution 4 - IosFarhan ArshadView Answer on Stackoverflow
Solution 5 - IosLew WinczynskiView Answer on Stackoverflow