繁体   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