Протокол може використовуватися лише як загальне обмеження, оскільки він має вимоги Self або асоційований тип


101

У мене є протокол RequestType, і він має асоційовану модель типу, як показано нижче.

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 або асоційований тип.

Відповіді:


152

Припустимо, на даний момент ми коригуємо ваш протокол, щоб додати підпрограму, яка використовує відповідний тип:

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

    func frobulateModel(aModel: Model)
}

І Свіфт мав дозволити вам створити масив RequestTypeтак, як вам хочеться. Я міг би передати масив цих типів запитів у функцію:

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

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

Я доходжу до того, що хочу все це обговорити, але мені потрібно знати, який аргумент передавати у дзвінок. Деякі з моїх RequestTypeорганізацій могли прийняти a LegoModel, деякі могли взяти a PlasticModel, а інші могли прийняти a 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
Тож ДОБРЕ пояснення, так одна відповідь.
Almas Adilbek,

1
Що означає фробуляція ?
Mofawaw

1
Ця відповідь стала чудовим виправданням для повного використання слова frobulate.
ScottyBlades

16

З 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

Невелика зміна дизайну вашого коду може зробити це можливим. Додайте порожній протокол non-associatedType у верхній частині ієрархії протоколів. Подобається це...

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

Свіфт 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
    }
    
}

І приклад View Controller:

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.