Protokol, Self veya relatedType gereksinimlerine sahip olduğu için yalnızca genel bir kısıtlama olarak kullanılabilir


102

Bir RequestType protokolüm var ve aşağıdaki gibi ilişkiliType Modeline sahip.

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

}

Şimdi tüm başarısız istekleri sıraya koymaya çalışıyorum.

public class RequestEventuallyQueue {

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

}

Ancak let queue = [RequestType](), Protokol RequestType'ın yalnızca genel bir kısıtlama olarak kullanılabileceğini, çünkü Self veya relatedType gereksinimlerine sahip olduğunu belirten hatayı alıyorum .

Yanıtlar:


152

Şu an için protokolünüzü ilişkili türü kullanan bir rutin eklemek için ayarladığımızı varsayalım:

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

    func frobulateModel(aModel: Model)
}

Ve Swift, RequestTypeistediğiniz şekilde bir dizi oluşturmanıza izin verecekti . Bu istek türlerini bir işleve aktarabilirim:

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

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

Her şeyi dondurmak istediğim noktaya geliyorum, ancak çağrıya ne tür bir argüman aktarılacağını bilmem gerekiyor. Varlıklarımdan bazıları RequestTypebir alabilir LegoModel, bazıları bir alabilir PlasticModelve diğerleri bir PeanutButterAndPeepsModel. Swift belirsizlikten memnun değildir, bu yüzden ilişkili bir tipe sahip bir protokol değişkenini bildirmenize izin vermez.

Aynı zamanda, örneğin, RequestTypehepsinin LegoModel. Bu makul görünüyor ve öyle ama bunu ifade etmek için bir yola ihtiyacın var.

Bunu yapmanın bir yolu, gerçek bir türü soyut Model türü adıyla ilişkilendiren bir sınıf (veya yapı veya enum) oluşturmaktır:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Şimdi bir dizi beyan etmek tamamen mantıklı LegoRequestTypeçünkü frobulatehepsine istersek LegoModelher seferinde bir geçmek zorunda kalacağımızı biliyoruz .

İlişkili Türlerdeki bu nüans, onları kullanan herhangi bir protokolü özel kılar. Swift Standard Kitaplığı, en çok bunun gibi Protokollere sahiptir Collectionveya Sequence.

CollectionStandart Kitaplık, protokolü uygulayan bir şeyler dizisi veya dizi protokolünü uygulayan bir dizi şey yaratmanıza izin vermek için, yapı tiplerini AnyCollection<T>veya AnySequence<T>. Tür silme tekniğini bir Stack Overflow yanıtında açıklamak oldukça karmaşıktır, ancak web'de arama yaparsanız, bununla ilgili birçok makale vardır.

Alex Gallagher'dan YouTube'da İlişkili Türlerle Protokoller (PAT'ler) üzerine bir video önerebilirim .


41
"çözümünüz çok genel " 😂
Adolfo

6
Bu, bu sorun için gördüğüm en iyi açıklamalardan biri
Keab42

1
Yani İYİ açıklama, çok tek cevap.
Almas Adilbek

1
Frobulate ne anlama geliyor?
Mofawaw

1
Bu cevap, frobulate kelimesinin kullanımını dondurmak için harika bir bahaneydi.
ScottyBlades

18

Swift 5.1'den - Xcode 11

Böyle bir şey elde etmek için opak bir sonuç türü kullanabilirsiniz.

bunu hayal edin:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Dolayısıyla aşağıdaki hata oluşturur:

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

Ancak, türden önce anahtar kelimeyi ekleyerek türü opak yapmak somesorunu çözecektir ve genellikle istediğimiz tek şey budur:

var objectA: some ProtocolA = ClassA()

4

Kodunuzun tasarımında küçük bir değişiklik bunu mümkün kılabilir. Protokol hiyerarşinizin en üstüne boş, ilişkisiz bir Tip protokolü ekleyin. Bunun gibi...

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'

}

Başka bir örnek, RequestType protokolünden türetilen sınıflarla, bir kuyruk oluşturmak ve kuyruğu uygun türü yazdırmak için bir işleve geçirmek

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

İlişkili bir tür ve temel protokol uygulayarak genel protokolleri nasıl kullanabileceğinize bir örnek :

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

Ve bir örnek 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

Bu hata aşağıdaki senaryoda da ortaya çıkabilir:

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

struct MyStuct {
    var myVar = MyProtocol
}

Bu durumda, sorunu çözmek için yapmanız gereken tek şey jenerik kullanmaktır:

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

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.