El protocolo solo se puede usar como una restricción genérica porque tiene requisitos propios o de tipo asociado

102

Tengo un RequestType de protocolo y tiene un modelo de tipo asociado como se muestra a continuación.

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

}

Ahora estoy tratando de hacer una cola de todas las solicitudes fallidas.

public class RequestEventuallyQueue {

    static let requestEventuallyQueue = RequestEventuallyQueue()
    let queue = [RequestType]()

}

Pero obtengo el error en línea de let queue = [RequestType]()que Protocol RequestType solo se puede usar como una restricción genérica porque tiene requisitos Self o AssociatedType.

Rahul Katariya
fuente

Respuestas:

152

Supongamos por el momento que ajustamos su protocolo para agregar una rutina que usa el tipo asociado:

public protocol RequestType: class {
    associatedtype Model
    var path: String { get set }

    func frobulateModel(aModel: Model)
}

Y Swift te permitiría crear una matriz de RequestTypela forma que quieras. Podría pasar una matriz de esos tipos de solicitud a una función:

func handleQueueOfRequests(queue: [RequestType]) {
    // frobulate All The Things!

    for request in queue {
       request.frobulateModel(/* What do I put here? */)
    }
}

Llego al punto en que quiero frobular todas las cosas, pero necesito saber qué tipo de argumento pasar a la llamada. Algunas de mis RequestTypeentidades podrían tomar una LegoModel, otras podrían tomar una PlasticModely otras podrían tomar una PeanutButterAndPeepsModel. Swift no está contento con la ambigüedad, por lo que no le permitirá declarar una variable de un protocolo que tenga un tipo asociado.

Al mismo tiempo, tiene mucho sentido, por ejemplo, crear una matriz de RequestTypecuando SABEMOS que todos usan el LegoModel. Esto parece razonable, y lo es, pero necesitas alguna forma de expresarlo.

Una forma de hacerlo es crear una clase (o estructura o enumeración) que asocie un tipo real con el nombre del tipo de modelo abstracto:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Ahora es completamente razonable declarar una matriz de LegoRequestTypeporque si quisiéramos frobulatetodas, sabemos que tendríamos que pasar una LegoModelcada vez.

Este matiz con los tipos asociados hace que cualquier protocolo que los utilice sea especial. La biblioteca estándar de Swift tiene protocolos como este más notablemente Collectiono Sequence.

Para permitirle crear una serie de cosas que implementan el Collectionprotocolo o un conjunto de cosas que implementan el protocolo de secuencia, la Biblioteca estándar emplea una técnica llamada "borrado de tipo" para crear los tipos de estructura AnyCollection<T>o AnySequence<T>. La técnica de borrado de tipo es bastante compleja de explicar en una respuesta de Stack Overflow, pero si busca en la web, hay muchos artículos al respecto.

Puedo recomendar un video de Alex Gallagher sobre protocolos con tipos asociados (PAT) en YouTube.

Scott Thompson
fuente
40
"tu solución es muy genérica " 😂
Adolfo
6
Esta es una de las mejores explicaciones que he visto para este problema
Keab42
1
Tan BUENA explicación, tan única respuesta.
Almas Adilbek
1
¿Qué significa frobulate ?
Mofawaw
1
Esta respuesta fue una gran excusa para frobular el uso de la palabra frobulate.
ScottyBlades
18

Desde Swift 5.1 - Xcode 11

Puede usar un tipo de resultado opaco para lograr algo así.

imagina esto:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Entonces lo siguiente genera el error:

var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */

Pero hacer que el tipo sea opaco agregando la somepalabra clave antes del tipo solucionará el problema y, por lo general, eso es lo único que queremos:

var objectA: some ProtocolA = ClassA()
Mojtaba Hosseini
fuente
4

Un pequeño cambio en el diseño de su código podría hacerlo posible. Agregue un protocolo vacío, non-associatedType, en la parte superior de su jerarquía de protocolos. Me gusta esto...

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'

}

Otro ejemplo, con clases derivadas del protocolo RequestType, haciendo una cola y pasando la cola a una función para imprimir el tipo apropiado

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)
Farhan Arshad
fuente
4

Swift 5.1

Un ejemplo de cómo puede usar protocolos genéricos implementando un tipo asociado y un protocolo base :

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
    }
    
}

Y un ejemplo de controlador de vista:

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

    
}
TimBigDev
fuente
0

Este error también puede ocurrir en el siguiente escenario:

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

struct MyStuct {
    var myVar = MyProtocol
}

En este caso, todo lo que tiene que hacer para solucionar el problema es usar genéricos:

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

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