У меня есть RequestType протокола, и у него есть модель AssociatedType, как показано ниже.

public protocol RequestType: class {

    associatedtype Model
    var path: String { get set }

}

public extension RequestType {

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

}

Сейчас пытаюсь поставить в очередь все неудавшиеся запросы.

public class RequestEventuallyQueue {

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

}

Но я получаю сообщение об ошибке в строке let queue = [RequestType] () этот протокол RequestType может использоваться только как общее ограничение, потому что он имеет требования Self или associatedType.

Ответы (5)

Предположим, что на данный момент мы настроим ваш протокол, чтобы добавить процедуру, которая использует связанный тип:

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

    func frobulateModel(aModel: Model)
}

И Swift позволил вам создать массив из RequestType так, как вы хотите. Я мог бы передать массив этих типов запросов в функцию:

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

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

Я дохожу до того, что хочу заморозить все, но мне нужно знать, какой тип аргумента передать в вызов. Некоторые из моих объектов RequestType могут принимать LegoModel, некоторые могут принимать PlasticModel, а другие могут принимать PeanutButterAndPeepsModel. Swift недоволен двусмысленностью, поэтому он не позволит вам объявить переменную протокола, имеющего связанный тип.

В то же время имеет смысл, например, создать массив из RequestType, когда мы ЗНАЕМ, что все они используют LegoModel. Это кажется разумным, и это так, но вам нужно как-то выразить это.

Один из способов сделать это - создать класс (или структуру, или перечисление), который связывает реальный тип с абстрактным именем типа модели:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Теперь вполне разумно объявить массив LegoRequestType, потому что, если бы мы хотели заморозить все они, как мы знаем, нам пришлось бы передать LegoModelкаждый раз.

Этот нюанс с ассоциированными типами делает любой протокол, который их использует, особенным. Стандартная библиотека Swift имеет такие протоколы, в первую очередь Collection или Sequence.

Чтобы позволить вам создать массив вещей, реализующих протокол Collection или набор вещей, реализующих протокол последовательности, Стандартная библиотека использует технику под названием «стирание типа» для создания структуры типы AnyCollection или AnySequence . Технику стирания типа довольно сложно объяснить в ответе на переполнение стека, но если вы поищете в Интернете, есть много статей об этом.

Я могу порекомендовать видео от Alex Gallagher о протоколах со связанными типами (PAT) на YouTube.

Эта ошибка также может возникнуть в следующем сценарии:

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

struct MyStuct {
    var myVar = MyProtocol
}

В этом случае все, что вам нужно сделать, чтобы исправить проблему, - это использовать дженерики:

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

struct MyStuct {
    var myVar = T
}

Из Swift 5.1 - Xcode 11

Вы можете использовать результат типа opaque, чтобы добиться чего-то подобного.

представьте себе:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Таким образом, следующая ошибка вызывает ошибку:

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

Но создание типа непрозрачным путем добавления ключевого слова some перед типом устранит проблему, и обычно это единственное, что нам нужно:

var objectA: some ProtocolA = ClassA()

Небольшое изменение дизайна вашего кода могло бы сделать это возможным. Добавьте пустой протокол, не связанный с типом, на вершину иерархии протоколов. Вот так ...

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'

}

Другой пример с классами, производными от протокола RequestType, создание очереди и передача очереди функции для печати соответствующего типа

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

var queue = [RequestTypeBase]()

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

queue.append(aRequest)

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

queue.append(bRequest)

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

queue.append(bURLRequest)

func showFailed(requests: [RequestTypeBase]){

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

    }
}

showFailed(requests: queue)

Swift 5.1

Пример , как вы можете использовать общие протоколы, реализовав связанный тип и базовый протокол:

import Foundation

protocol SelectOptionDataModelProtocolBase: class{}

protocol SelectOptionDataModelProtocol: SelectOptionDataModelProtocolBase {
    associatedtype T
    
    var options: Array { get }
    
    var selectedIndex: Int { get set }
    
}

class SelectOptionDataModel: SelectOptionDataModelProtocol {
    typealias T = A
    
    var options: Array
    
    var selectedIndex: Int
    
    init(selectedIndex _selectedIndex: Int, options _options: Array) {
        self.options = _options
        self.selectedIndex = _selectedIndex
    }
    
}

И пример контроллера представления:

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(selectedIndex: 0, options: ["option 1", "option 2", "option 3"])
        self.dataModel2 = SelectOptionDataModel(selectedIndex: 0, options: [1, 2, 3])
        self.dataModel3 = SelectOptionDataModel(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

    
}

2022 WebDevInsider