Протокол можно использовать только в качестве общего ограничения, поскольку он имеет собственные или связанные требования типа
У меня есть протокол 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)