【问题标题】:How can have an instance or a return type of a generic type conforming to a protocol with associated value如何拥有符合具有关联值的协议的泛型类型的实例或返回类型
【发布时间】:2020-10-31 15:03:02
【问题描述】:

所以我不知道标题是否正确解决了我的问题,但事情是这样的:

我有一个用于识别几种类型的表格视图单元格的枚举。每种类型的细胞都有自己的类。我已经使其中的每一个都符合协议,但具有关联的类型。我现在正在尝试创建一种方法来拥有这些类的实例并任意使用它们的协议方法和属性。这是一个例子:

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 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。我已经构建没有任何问题但还不能测试(测试服务器在工作时间之外关闭)。当我测试它并且如果它运行没有任何问题时,我会将它作为解决方案发布。

【问题讨论】:

    标签: ios swift uitableview swift-protocols


    【解决方案1】:

    您似乎正在尝试将多态行为(即单元类型数组)与静态行为(具有关联类型的协议)混合在一起,而这在当前的 Swift 设计中或多或少是不可能的。

    具有关联类型的协议是在编译时确定的,而 tableView(_:cellForRowAt:) 方法中的代码是在运行时执行的,这使得它与具有关联类型的协议有些不兼容。

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

    在上面的代码中,您需要能够告诉编译器您希望单元格使用哪种类型,以便注入委托,但这是编译时功能,您在运行时需要它,因此不兼容.

    恐怕您必须为此坚持运行时错误传播/处理。除非您愿意更改 delegate 以匹配整个可能的委托集,并且在这种情况下不再需要关联的类型,从而启用运行时功能。

    protocol MyCell: UITableViewCell {
        var delegate: CellDelegate1 & CellDelegate2 ... & CellDelegateN { get set }
    
        func setupCell(SomeCommonProtocolThatGetsCastedAtRuntime)
    }
    

    【讨论】:

    • 嗯,谢谢你的回答。它没有解决我的问题,但它让我了解了如何解决它。
    【解决方案2】:

    解决方案

    所以经过几天的工作和试错,我终于想出了一个解决方案。感谢@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,在枚举中为它创建一个案例,将案例添加到无论何时何地需要数组,注册单元格 id 和 瞧! 它就在那里!并且由于委托,单元现在知道所有需要的属性,因此他们可以轻松地使用像他们自己的属性一样的属性,唯一的区别是它不是self.something,而是delegate?.something

    我希望我的这一努力可能对其他人有所帮助。干杯!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-05
      • 2020-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多