โปรโตคอลสามารถใช้เป็นข้อ จำกัด ทั่วไปเท่านั้นเนื่องจากมีข้อกำหนดในตัวเองหรือประเภทที่เกี่ยวข้อง


106

ฉันมีโปรโตคอล RequestType และมี AssociatedType Model ดังต่อไปนี้

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 สามารถใช้เป็นข้อ จำกัด ทั่วไปเท่านั้นเนื่องจากมีข้อกำหนดในตัวเองหรือประเภทที่เกี่ยวข้อง

คำตอบ:


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เอนทิตีอาจใช้ a LegoModelบางส่วนอาจใช้ a PlasticModelและคนอื่น ๆ อาจใช้ aPeanutButterAndPeepsModel . Swift ไม่พอใจกับความคลุมเครือดังนั้นจึงไม่อนุญาตให้คุณประกาศตัวแปรของโปรโตคอลที่มีประเภทที่เกี่ยวข้อง

ในขณะเดียวกันมันก็สมเหตุสมผลเช่นสร้างอาร์เรย์ของ RequestTypeเมื่อเรารู้ว่าทั้งหมดใช้นามสกุลLegoModel. สิ่งนี้ดูสมเหตุสมผล แต่คุณต้องมีวิธีแสดงออก

วิธีหนึ่งในการทำเช่นนั้นคือการสร้างคลาส (หรือโครงสร้างหรือ enum) ที่เชื่อมโยงประเภทจริงกับชื่อประเภทโมเดลนามธรรม:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

ตอนนี้มันสมเหตุสมผลอย่างยิ่งที่จะประกาศอาร์เรย์ของ LegoRequestTypeเพราะถ้าเราต้องการให้frobulateพวกเขาทั้งหมดเรารู้ว่าเราจะต้องผ่านในLegoModelแต่ละครั้ง

ความแตกต่างเล็กน้อยกับประเภทที่เกี่ยวข้องนี้ทำให้โปรโตคอลใด ๆ ที่ใช้เป็นพิเศษ Swift Standard Library มีโปรโตคอลเช่นนี้ที่โดดเด่นที่สุดCollectionหรือSequence.

เพื่อให้คุณสามารถสร้างอาร์เรย์ของสิ่งที่ใช้Collectionโปรโตคอลหรือชุดของสิ่งที่ใช้โปรโตคอลลำดับ Standard Library ใช้เทคนิคที่เรียกว่า "type-erasure" เพื่อสร้างประเภทของโครงสร้างAnyCollection<T>AnySequence<T>หรือ เทคนิคการลบประเภทค่อนข้างซับซ้อนที่จะอธิบายด้วยคำตอบ Stack Overflow แต่ถ้าคุณค้นหาในเว็บจะมีบทความมากมายเกี่ยวกับเรื่องนี้

ฉันสามารถแนะนำวิดีโอจากAlex Gallagher เรื่อง Protocols With Associated types (PATs)บน YouTube


42
"วิธีแก้ปัญหาของคุณธรรมดามาก " 😂
Adolfo

7
นี่เป็นหนึ่งในคำอธิบายที่ดีที่สุดที่ฉันเคยเห็นสำหรับปัญหานี้
Keab42

2
คำอธิบายที่ดีดังนั้นคำตอบเดียว
Almas Adilbek

1
อะไรfrobulateเฉลี่ย?
Mofawaw

1
ในช่วงทศวรรษที่ 1980 มีซีรีส์เกมผจญภัยแบบข้อความที่เริ่มต้นด้วยเกม Zork ในซีรีส์เกมนั้นมี Frobozz Magic Company พวกเขาเคยกบดาน ในระยะสั้นมันเป็นวลีที่ไร้สาระสำหรับการกระทำที่ไม่เฉพาะเจาะจง
Scott Thompson

19

จาก 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()

5

สวิฟต์ 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

    
}

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)

0

ข้อผิดพลาดนี้อาจเกิดขึ้นในสถานการณ์ต่อไปนี้:

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

struct MyStuct {
    var myVar = MyProtocol
}

ในกรณีนี้สิ่งที่คุณต้องทำเพื่อแก้ไขปัญหาคือใช้ generics:

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

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.