Protokol hanya dapat digunakan sebagai batasan umum karena memiliki persyaratan Self atau relatedType

106

Saya memiliki RequestType protokol dan memiliki Model terkait seperti di bawah ini.

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

}

Sekarang saya mencoba membuat antrian dari semua permintaan yang gagal.

public class RequestEventuallyQueue {

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

}

Tetapi saya mendapatkan kesalahan pada baris let queue = [RequestType]()bahwa Protocol RequestType hanya dapat digunakan sebagai batasan umum karena memiliki persyaratan Self atau relatedType.

Rahul Katariya
sumber

Jawaban:

152

Misalkan untuk saat ini kami menyesuaikan protokol Anda untuk menambahkan rutinitas yang menggunakan tipe terkait:

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

    func frobulateModel(aModel: Model)
}

Dan Swift mengizinkan Anda membuat larik RequestTypesesuai keinginan Anda. Saya bisa melewatkan berbagai jenis permintaan tersebut ke dalam sebuah fungsi:

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

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

Saya sampai pada titik bahwa saya ingin mengacaukan semua hal, tetapi saya perlu tahu jenis argumen apa yang harus dimasukkan ke dalam panggilan. Beberapa RequestTypeentitas saya dapat mengambil LegoModel, beberapa dapat mengambil PlasticModel, dan lainnya dapat mengambil PeanutButterAndPeepsModel. Swift tidak senang dengan ambiguitas tersebut sehingga tidak akan membiarkan Anda mendeklarasikan variabel protokol yang memiliki tipe terkait.

Pada saat yang sama, sangat masuk akal untuk, misalnya, membuat larik RequestTypeketika kita TAHU bahwa semuanya menggunakan LegoModel. Ini tampaknya masuk akal, dan memang demikian, tetapi Anda memerlukan cara untuk mengungkapkannya.

Salah satu cara untuk melakukannya adalah dengan membuat kelas (atau struct, atau enum) yang mengaitkan tipe nyata dengan nama tipe Model abstrak:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Sekarang sepenuhnya masuk akal untuk mendeklarasikan sebuah array LegoRequestTypekarena jika kita ingin frobulatesemuanya, kita tahu kita harus melewatkannya LegoModelsetiap saat.

Nuansa dengan Jenis Terkait ini membuat protokol apa pun yang menggunakannya menjadi istimewa. Perpustakaan Standar Swift memiliki Protokol seperti ini yang paling terkenal Collectionatau Sequence.

Untuk memungkinkan Anda membuat larik hal-hal yang mengimplementasikan Collectionprotokol atau sekumpulan hal-hal yang mengimplementasikan protokol sekuens, Perpustakaan Standar menggunakan teknik yang disebut "jenis-penghapusan" untuk membuat jenis struktur AnyCollection<T>atau AnySequence<T>. Teknik penghapusan jenis agak rumit untuk dijelaskan dalam jawaban Stack Overflow, tetapi jika Anda menelusuri web, ada banyak artikel tentangnya.

Saya dapat merekomendasikan video dari Alex Gallagher tentang Protocols With Associated Types (PATs) di YouTube.

Scott Thompson
sumber
42
"solusi Anda sangat umum " 😂
Adolfo
7
Ini adalah salah satu penjelasan terbaik yang pernah saya lihat untuk masalah ini
Keab42
2
Penjelasan yang sangat BAIK, jawaban yang sangat tunggal
Almas Adilbek
1
Apa frobulate artinya?
Mofawaw
1
Pada 1980-an ada seri game petualangan teks yang dimulai dengan game Zork. Di seri game itu ada Frobozz Magic Company. Mereka dulu sering mengotak-atik sesuatu. Singkatnya, ini adalah ungkapan konyol untuk tindakan yang tidak spesifik.
Scott Thompson
19

Dari Swift 5.1 - Xcode 11

Anda dapat menggunakan jenis hasil buram untuk mencapai sesuatu seperti itu.

bayangkan ini:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Jadi yang berikut ini menghasilkan kesalahan:

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

Tetapi membuat tipe menjadi buram dengan menambahkan somekata kunci sebelum tipe akan memperbaiki masalah dan biasanya hanya itulah yang kami inginkan:

var objectA: some ProtocolA = ClassA()
Mojtaba Hosseini
sumber
5

Swift 5.1.0

Sebuah contoh bagaimana Anda dapat menggunakan protokol generik dengan menerapkan jenis terkait dan protokol dasar :

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

Dan contoh 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

    
}
TimBigDev
sumber
4

Sedikit perubahan dalam desain kode Anda dapat memungkinkannya. Tambahkan protokol kosong, jenis tidak terkait, di bagian atas hierarki protokol Anda. Seperti ini...

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'

}

Contoh lain, dengan kelas-kelas yang diturunkan dari protokol RequestType, membuat antrian dan meneruskan antrian ke suatu fungsi untuk mencetak tipe yang sesuai.

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)
Farhan Arshad
sumber
0

Galat ini juga dapat terjadi dalam skenario berikut ini:

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

struct MyStuct {
    var myVar = MyProtocol
}

Dalam kasus ini, yang harus Anda lakukan untuk memperbaiki masalah ini adalah menggunakan obat generik:

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

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Lew Winczynski
sumber