Giao thức chỉ có thể được sử dụng như một ràng buộc chung vì nó có các yêu cầu Kiểu tự hoặc kiểu liên kết


101

Tôi có một RequestType giao thức và nó có Mô hình Liên kết như bên dưới.

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

}

Bây giờ tôi đang cố tạo một hàng đợi tất cả các yêu cầu không thành công.

public class RequestEventuallyQueue {

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

}

Nhưng tôi gặp lỗi trực tuyến let queue = [RequestType]()rằng Protocol RequestType chỉ có thể được sử dụng như một ràng buộc chung vì nó có các yêu cầu Self hoặc linkedType.

Câu trả lời:


152

Giả sử hiện tại chúng tôi điều chỉnh giao thức của bạn để thêm một quy trình sử dụng loại được liên kết:

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

    func frobulateModel(aModel: Model)
}

Và Swift cho phép bạn tạo ra một mảng RequestTypetheo cách bạn muốn. Tôi có thể chuyển một mảng các loại yêu cầu đó vào một hàm:

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

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

Tôi suy sụp đến mức muốn đóng băng tất cả mọi thứ, nhưng tôi cần biết loại đối số nào để chuyển vào cuộc gọi. Một số RequestTypethực thể của tôi có thể lấy a LegoModel, một số có thể lấy a PlasticModelvà những thực thể khác có thể lấy a PeanutButterAndPeepsModel. Swift không hài lòng với sự mơ hồ vì vậy nó sẽ không cho phép bạn khai báo một biến của giao thức có kiểu liên kết.

Đồng thời, nó có ý nghĩa hoàn hảo, chẳng hạn như tạo một mảng RequestTypekhi chúng ta BIẾT rằng tất cả chúng đều sử dụng LegoModel. Điều này có vẻ hợp lý, và đúng là như vậy, nhưng bạn cần một số cách để thể hiện điều đó.

Một cách để làm điều đó là tạo một lớp (hoặc struct, hoặc enum) liên kết một kiểu thực với tên kiểu Mô hình trừu tượng:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Bây giờ hoàn toàn hợp lý khi khai báo một mảng LegoRequestTypebởi vì nếu chúng ta muốn frobulatetất cả chúng, chúng ta biết rằng chúng ta sẽ phải vượt qua LegoModelmỗi lần.

Sắc thái này với các Loại liên kết làm cho bất kỳ giao thức nào sử dụng chúng trở nên đặc biệt. Các Swift thư viện chuẩn có giao thức như thế này đáng chú ý nhất Collectionhoặc Sequence.

Để cho phép bạn tạo một mảng những thứ triển khai Collectiongiao thức hoặc một tập hợp những thứ triển khai giao thức trình tự, Thư viện Chuẩn sử dụng một kỹ thuật được gọi là "type-erasure" để tạo các kiểu struct AnyCollection<T>hoặc AnySequence<T>. Kỹ thuật xóa kiểu chữ khá phức tạp để giải thích trong câu trả lời Stack Overflow, nhưng nếu bạn tìm kiếm trên web sẽ có rất nhiều bài viết về nó.

Tôi có thể đề xuất một video từ Alex Gallagher về Giao thức với các loại được liên kết (PATs) trên YouTube.


40
"giải pháp của bạn rất chung chung " 😂
Adolfo

6
Đây là một trong những lời giải thích tốt nhất mà tôi đã thấy cho vấn đề này
Keab42

1
Vì vậy, lời giải thích TỐT, vì vậy câu trả lời duy nhất.
Almas Adilbek

1
Những gì hiện frobulate nghĩa là gì?
Mofawaw

1
Câu trả lời này là một lý do tuyệt vời để sử dụng từ cóng.
ScottyBlades

17

Từ Swift 5.1 - Xcode 11

Bạn có thể sử dụng kiểu kết quả không rõ ràng để đạt được điều gì đó tương tự.

hãy tưởng tượng điều này:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Vì vậy, điều sau đây tạo ra lỗi:

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

Nhưng làm cho loại không rõ ràng bằng cách thêm sometừ khóa trước loại sẽ khắc phục sự cố và thường đó là điều duy nhất chúng tôi muốn:

var objectA: some ProtocolA = ClassA()

4

Một chút thay đổi trong thiết kế mã của bạn có thể làm cho nó thành hiện thực. Thêm một giao thức trống, không liên kếtType, ở đầu hệ thống phân cấp giao thức của bạn. Như thế này...

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'

}

Một ví dụ khác, với các lớp bắt nguồn từ giao thức RequestType, tạo một hàng đợi và chuyển hàng đợi đến một hàm để in ra loại thích hợp

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

Một ví dụ về cách bạn có thể sử dụng các giao thức chung bằng cách triển khai một loại được liên kếtgiao thức cơ sở :

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

Và một ví dụ 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

Lỗi này cũng có thể xảy ra trong trường hợp sau:

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

struct MyStuct {
    var myVar = MyProtocol
}

Trong trường hợp này, tất cả những gì bạn phải làm để khắc phục sự cố là sử dụng thuốc generic:

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

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.