簡體   English   中英

如何擁有符合具有關聯值的協議的泛型類型的實例或返回類型

[英]How can have an instance or a return type of a generic type conforming to a protocol with associated value

所以我不知道標題是否正確解決了我的問題,但事情是這樣的:

我有一個用於識別幾種類型的表格視圖單元格的枚舉。 每種類型的細胞都有自己的類。 我已經將其中的每一個都符合一個協議,但具有關聯的類型。 我現在正在嘗試創建一種方法來擁有這些類的實例並任意使用它們的協議方法和屬性。 下面是一個例子:

enum CellType {
    case a, b, c
   
    func getCellClass() -> ProtocolWithAssociatedType.Type { //Error here
        switch self {
        case .a: return ClassA.self
        case .b: return ClassB.self
        case .c: return ClassC.self
    }
}

此枚舉引發了Protocol 'CreateOrderTableViewCellProtocol' can only be used as a generic constraint because it has Self or associated type requirements的錯誤, Protocol 'CreateOrderTableViewCellProtocol' can only be used as a generic constraint because it has Self or associated type requirements在該行上Protocol 'CreateOrderTableViewCellProtocol' can only be used as a generic constraint because it has Self or associated type requirements

所以這是我除了名字之外的確切協議:

protocol ProtocolWithAssociatedType: UITableViewCell {
    associatedtype Delegate
    var delegate: Delegate? {get set}
    func setupCell()
}

所有的類 ClassA、ClassB 和 ClassC 都符合這一點。 它們都有自己的委托協議,它們使用類型typealias例如:

protocol ClassADelegate: class {
...
}

class ClassA: UITableViewCell, ProtocolWithAssociatedType {
    typealias Delegate = ClassADelegate
    weak var delegate: ClassADelegate?
    func setupCell() {}

    ...
}

extension ViewController: ClassADelegate {
...
}

所有這些都是為了精簡 tableView(...cellForItemAt:...) 和其他類似的方法,因為這個項目中有很多單元格類,超出了可讀性,真的很難在上面做任何開發因為這個,這個特定的視圖控制器。 我有一個 UITableView 擴展,用於為那些可重用 id 與類名相同的單元創建可重用單元,如下所示:

func dequeueReusableCell<C>(_ cellType: C.Type, for indexPath: IndexPath) -> C where C: UITableViewCell {
    let identifier = String(describing: cellType)
        
    guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? C else {
        fatalError("Failed to find cell with identifier \"\(identifier)\" of type \"\(C.self)\"")
    }
        return cell
}

所以我願意像下面這樣使用它:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellType = CellDataArray[indexPath.row].cellType
    let cellClass = cellType.getCellClass()

    let cell = tableView.dequeueReusableCell(cellClass, for: indexPath) \\ Here I have to have the cell conform to the protocol somehow

    cell.delegate = self \\So here is the associated type which doesn't need to be read but is need to be written only
    cell.setupCell() \\Obviously there are going to be some data passed to the cell instance right here
    return cell
}

我已經閱讀了很多問題和答案,我正在嘗試每個問題和答案來完成此任務,但我一直無法做到。 我試圖避免視圖控制器中的大量功能,並使事情盡可能可修改。 所有這些單元格本身就像小視圖控制器一樣,它們都必須與視圖控制器通信。 單元格也不是靜態的,就像在不同的場合我必須展示不同的單元格一樣。 現在,即使添加一個簡單的單元格也是一項艱巨的工作,更不用說創造一個全新的場合了。 但是通過這種方法,我試圖讓事情變得......模塊化。

那么有沒有什么方法可以做到這一點,而不會在這里和那里運行時崩潰或創建一個黑洞並使宇宙終結?

編輯:我嘗試了泛型類型的方式,但無法這樣做。 由於我希望func getCellClass()工作的方式,不可能讓編譯器知道通用類型是什么。 像下面這樣:

func getCellClass<C>() -> C.Type where C: ProtocolWithAssociatedValue {
...
}

所以即使我強制將返回值轉換為C.Type然后我也有問題,我只是因為 C 未知而調用它。

編輯

我已經從協議中刪除了相關的值並執行了以下操作:

protocol ProtocolWithAssociatedType: UITableViewCell {
    var _delegate: AnyObject? {get set}
    func setupCell()
}
protocol ClassADelegate: class {
...
}

class ClassA: UITableViewCell, ProtocolWithAssociatedType {
    weak var _delegate: AnyObject? { didSet { delegate = _delegate as? ClassADelegate } }
    private weak var delegate: ClassADelegate?
    func setupCell() {}

    ...
}

extension ViewController: ClassADelegate {
...
}

這樣,協議可用作該枚舉函數的返回類型,並且我可以強制將可重用單元格強制轉換為它,因為協議本身符合 UITableViewCell。 我的構建沒有任何問題,但還不能測試(測試服務器在工作時間之外停機)。 當我測試它並且如果它運行沒有任何問題時,我會將其發布為解決方案。

似乎您正在嘗試將多態行為(即單元格類型的數組)與靜態行為(具有關聯類型的協議)混合在一起,這在當前的 Swift 設計中或多或少是不可能的。

具有關聯類型的協議在編譯時確定,而tableView(_:cellForRowAt:)方法中的代碼在運行時執行,這使得它與具有關聯類型的協議有些不兼容。

let cell = tableView.dequeueReusableCell(cellClass, for: indexPath)

在上面的代碼中,您需要能夠告訴編譯器您期望單元格的類型,以便注入委托,但這是一個編譯時功能,您在運行時需要它,因此不兼容。

恐怕您將不得不為此堅持運行時錯誤傳播/處理。 除非您願意更改delegate以匹配整個可能的委托集,並且在這種情況下不再需要關聯類型,這將啟用運行時功能。

protocol MyCell: UITableViewCell {
    var delegate: CellDelegate1 & CellDelegate2 ... & CellDelegateN { get set }

    func setupCell(SomeCommonProtocolThatGetsCastedAtRuntime)
}

解決方案

因此,經過幾天的工作和反復試驗,我終於想出了一個解決方案。 感謝@Cristik,我有了洞察力並改變了我的方法。

首先,我改變了處理代表的方式。 每個單元格都有一個不同的代表。 現在我只有一個所有這些細胞都使用的。

因此,通過這種方式,我能夠使我的財產如下所示:

protocol CellProtocol: UITableViewCell {
    var delegate: CommonCellDelegate?
    func setupCell(...)
}

對於單元格將使用的視圖控制器的所有屬性和一些常用方法,我已經聲明了如下委托協議:

protocol CommonCellDelegate: class {
    var viewControllerProperty1: SomeClass1 { get set }
    var viewControllerProperty2: SomeClass2 { get set }
    ...
    var viewControllerPropertyN: SomeClassN { get set }

    func someCommonFunction1(...) -> ...
    func someCommonFunction2(...) -> ...
    ...
    func someCommonFunctionN(...) -> ...
}

由於我從一開始的意圖是保持視圖控制器的美觀和干凈,我將那些單元格單獨需要的不常見方法放在這些單元格的類聲明下方,作為委托屬性的擴展:

class SomeCellClass: CellProtocol {
    var delegate: CommonCellDelegate?
    func setupCell(...) {
    ...
    }

    ...
}
extension CommonCellDelegate {
    func someMethodForTheCellAbove1(...) -> ... {
    ...
    }
    func someMethodForTheCellAbove2(...) -> ... {
    ...
    }
    .
    .
    .
}

由於我不再需要 enum 方法來返回不同的類,因此我對其進行了一些更改以返回每種單元格類型的重用 ID。 所以不需要在那里返回類類型:

enum CellType {
    case cellA, cellB, cellC, ...
   
    func getCellClass() -> String { //Error here
        switch self {
        case .cellA: return String(describing: ClassA.self)
        case .cellB: return String(describing: ClassB.self)
        case .cellC: return String(describing: ClassC.self)
        ...
        // Note: We use the the name of the cell classes as their reuse id's for simplicity.
    }
}

最后,我能夠很容易地在tableView(_:cellForRowAt:)方法中創建這些單元格,並且不會在時空結構中創建撕裂:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellType = CellDataArray[indexPath.row].cellType
    let cellReuseId = String(describing: cellType.getCellId())

    guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) as? CellProtocol else { fatalError("some fatal error") }

    cell.delegate = self
    cell.setupCell(...)
    return cell
}

這樣就可以很容易地創建新的實現,因為所有需要做的就是創建類,使類符合CellProtocol ,在枚舉中CellProtocol創建一個案例,無論何時何地都將案例添加到數組中需要,注冊小區 ID 和瞧! 在那! 由於委托,所有需要的屬性現在都被單元知道了,他們可以很容易地使用那些就像他們自己的屬性一樣,唯一的區別是它不是self.something而是delegate?.something

我希望我的這一努力可以對其他人有所幫助。 干杯!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM