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


102

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

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]()что протокол RequestType может использоваться только как общее ограничение, потому что он имеет требования Self или associatedType.

Ответы:


152

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

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потому что, если бы мы хотели их frobulateвсе, мы знали бы, что нам пришлось бы LegoModelкаждый раз передавать .

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

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

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


40
«ваше решение очень общее » 😂
Адольфо

6
Это одно из лучших объяснений, которые я видел для этой проблемы
Keab42

1
Так ХОРОШЕЕ объяснение, такой однозначный ответ.
Алмас Адильбек

1
Что значит фробулировать ?
Mofawaw,

1
Этот ответ был отличным поводом для того, чтобы использовать слово «фробулат».
ScottyBlades

18

Из Swift 5.1 - Xcode 11

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

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

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

4

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

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)

4

Swift 5.1

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

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

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

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

    
}

0

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

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

struct MyStuct {
    var myVar = MyProtocol
}

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

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

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.