【问题标题】:Use protocol with constrained associated type as property in Swift在 Swift 中使用具有约束关联类型的协议作为属性
【发布时间】:2018-08-16 15:00:20
【问题描述】:

我正在尝试实现具有关联类型的数据源协议

protocol DataSourceCompatible {
    associatedtype CellModel
    func cellModelForItem(at indexPath: IndexPath) -> CellModel
}

协议AddressBookViewModelType 继承自基本协议并将关联的值约束到另一个协议

protocol AddressBookViewModelType: class, DataSourceCompatible where CellModel == AddressBookCellModelType {
}

AddressBookViewModelAddressBookViewModelType协议的具体实现

class AddressBookViewModel: AddressBookViewModelType {
    func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModelType {
        let contact = sectionedContacts[indexPath.section][indexPath.row]
        return AddressBookCellModel(contact: contact)
    }
}

代码编译得很好,但是当我将视图模型声明为视图控制器上的属性时,编译器会失败并显示Protocol 'AddressBookViewModelType' can only be used as a generic constraint because it has Self or associated type requirements

class AddressBookViewController: UIViewController {
    private var viewModel: AddressBookViewModelType!

    func configure(viewModel: AddressBookViewModelType) {
        self.viewModel = viewModel
    }
...
}

我记得看到类型擦除可能会解决问题,但我对类型擦除概念并不熟悉。有没有办法解决这个问题?

更新:

AddressBookCellModelType 和 AddressBookCellModel 在这里是如何关联的?

这是一个实现协议的结构体。

protocol AddressBookCellModelType {
    var name: String { get }
    var photo: UIImage? { get }
    var isInvited: Bool { get }
}

struct AddressBookCellModel: AddressBookCellModelType {
 ....
}

【问题讨论】:

  • 这是一个非常复杂的协议使用。混合类继承、协议继承和 PAT 充满了极端情况和惊喜。您是否期望 AddressBookViewModelType 的多个实现?如果没有,首先减少协议的数量并消除尽可能多的 PAT,并专注于泛型,或者如果您实际上不涉及多种类型,则只关注简单的非泛型代码。主动让 Swift 变得高度通用(即在你对它有明确要求之前)充满了锋利的边缘。
  • AddressBookCellModelTypeAddressBookCellModel 在这里有什么关系?那肯定感觉像是在滥用协议(试图使用协议来重新创建类继承,这不是他们的意图)。
  • @RobNapier,请查看问题的更新
  • AddressBookCellModelType 在您的代码中提供什么用途?实现是什么样的?它使您付出了很多复杂性,并且很难想象它提供了什么好处。这感觉就像一个纯粹为了避免具体类型而存在的协议,这总是一个错误。一个简单的结构是一个非常好的类型。

标签: swift generics swift-protocols associated-types


【解决方案1】:

您是否尝试过仅将其用作泛型,如警告/错误所说:

class AddressBookViewController<T: AddressBookViewModelType> : UIViewController {
    private var viewModel: T!

    func configure(viewModel: T) {
        self.viewModel = viewModel
    }
    ...
}

您需要使用变量T 的属性初始化控制器,以便推断类型。

【讨论】:

    【解决方案2】:

    为了扩展我在 cmets 中的问题,看看这段代码,它看起来就像不添加 AddressBookCellModelTypeAddressBookViewModelType 一样灵活,这也可以摆脱头痛,同时仍然是通用的DataSourceCompatible.

    // This protocol is fine and very useful for making reusable view controllers. Love it.
    protocol DataSourceCompatible {
        associatedtype CellModel
        func cellModelForItem(at indexPath: IndexPath) -> CellModel
    }
    
    // No need for a protocol here. The struct is its own interface.
    // This ensures value semantics, which were being lost behind the protocol
    // (since a protocol does not promise value semantics)    
    struct AddressBookCellModel {
        var name: String
        var photo: UIImage?
        var isInvited: Bool
    }
    
    // AddressBookViewModel conforms to DataSourceCompatible
    // Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
    class AddressBookViewModel: DataSourceCompatible {
        let sectionedContacts: [[AddressBookCellModel]] = []
        func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
            return sectionedContacts[indexPath.section][indexPath.row]
        }
    }
    
    class AddressBookViewController: UIViewController {
        private var viewModel: AddressBookViewModel!
    
        func configure(viewModel: AddressBookViewModel) {
            self.viewModel = viewModel
        }
    }
    

    这样做允许一个通用的 VC,而无需引入更多所需的部分:

    class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
        private var viewModel: DataSource.CellModel!
    
        func configure(viewModel: DataSource.CellModel) {
            self.viewModel = viewModel
        }
    }
    
    let vc = DataSourceViewController<AddressBookViewModel>()
    

    【讨论】:

    • 感谢 Rob 的解释,我希望无需使 VC 通用化即可解决问题。我想我需要按照你的建议减少抽象的数量:)
    【解决方案3】:

    这只是 Swift 规范,您不能使用“具有关联类型的协议”作为类型声明。原因是编译器在编译时无法知道关联的类型实际上是什么,这违反了 Swift 的“类型安全”。

    解决方案是像你说的那样使用类型橡皮擦,或者使类型通用。

    【讨论】:

    • 投反对票,因为没有例子,只是一个想法的暗示,他已经有了(类型橡皮擦)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多