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

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

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

}

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

public class RequestEventuallyQueue {

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

}

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

Ответы

Ответ 1

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

 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? */)
    }
}

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

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

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

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

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

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

Чтобы создать массив объектов, реализующих протокол Collection или набор элементов, реализующих протокол последовательности, в стандартной библиотеке используется метод "стирание типа" для создания типов структур AnyCollection<T> или AnySequence<T>. Метод стирания типа довольно сложный для объяснения в ответе "Переполнение стека", но если вы ищете в Интернете, есть много статей об этом.

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

Ответ 2

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

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