检查两个对象是否实现Swift协议及其关联类型
我有一个Swift 3项目,我在其中声明一个协议,其关联类型如下:检查两个对象是否实现Swift协议及其关联类型,swift,swift-protocols,associated-types,Swift,Swift Protocols,Associated Types,我有一个Swift 3项目,我在其中声明一个协议,其关联类型如下: protocol ViewModelContainer { associatedtype ViewModelType var viewModel: ViewModelType! { get set } } if let container = container as? ViewModelContainer, let model = model as? container.ViewModelType {
protocol ViewModelContainer {
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
if let container = container as? ViewModelContainer, let model = model as? container.ViewModelType {
container.viewModel = model
}
我想检查两个对象是否实现了ViewModelContainer
及其关联类型ViewModelType
,以“通用”方式进行赋值
理想情况下,我想做这样的事情:
protocol ViewModelContainer {
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
if let container = container as? ViewModelContainer, let model = model as? container.ViewModelType {
container.viewModel = model
}
但是我不能将容器
转换为视图模型容器
:
协议“ViewModelContainer”只能用作一般约束,因为它具有自身或关联的类型要求
我目前的解决方法是直接返回到特定类及其关联类型,但这会使我的代码非常冗长且容易出错:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? MediaPlaySelectionViewController, let vm = sender as? MediaPlaySelectionViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? SearchResultsViewController, let vm = sender as? SearchResultsViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? ReviewDetailsViewController, let vm = sender as? ReviewDetailsViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? ReviewComposerViewController, let vm = sender as? ReviewComposerViewModel {
vc.viewModel = vm
}
}
我尝试使用通用的
UIViewController
s,但由于无法在情节提要中使用而陷入困境。以下是将关联类型ViewModelType
更改为协议的想法
protocol ViewModelProtocol {
}
protocol ViewModelContainer {
var viewModel: ViewModelProtocol? { get set }
}
class MediaPlaySelectionViewModel: ViewModelProtocol {
var title: String?
func play() {
print("playing")
}
}
class SearchResultsViewModel: ViewModelProtocol {
var results: [String]?
}
class MediaPlaySelectionViewController: UIViewController, ViewModelContainer {
var viewModel: ViewModelProtocol?
// So the view itself know which kind of vm it wants.
var myViewModel: MediaPlaySelectionViewModel? {
return viewModel as? MediaPlaySelectionViewModel
}
override func viewWillAppear(_ animated: Bool) {
print(myViewModel?.title ?? "Undefined")
}
}
class SearchResultsViewController: UIViewController, ViewModelContainer {
var viewModel: ViewModelProtocol?
// So the view itself know which kind of vm it wants.
var myViewModel: SearchResultsViewModel? {
return viewModel as? SearchResultsViewModel
}
override func viewWillAppear(_ animated: Bool) {
print(myViewModel?.results?.joined(separator: ", ") ?? "No Result")
}
}
class MenuViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// NOTE: Swift doesn't allow me to use 'let' here
if var container = segue.destination as? ViewModelContainer, let cell = sender as? UITableViewCell, let vm = viewModel(for: cell) {
container.viewModel = vm
}
}
// NOTE: One difficulty here, how could you decide which ViewModel to prepare? I guess you need a Factory.
func viewModel(for cell: UITableViewCell) -> ViewModelProtocol! {
if let index = tableView.indexPath(for: cell) {
if index.item == 0 {
let vm = MediaPlaySelectionViewModel()
vm.title = "My Video"
return vm
}
else if index.item == 1 {
let vm = SearchResultsViewModel()
vm.results = ["Apple", "Banana"]
return vm
}
}
return nil
}
}
这比我预期的要复杂(因此我删除了我以前的帖子以避免混淆),但我相信这对你来说应该是可行的:
protocol ViewModelContainerVC
{
mutating func setModel(_ :Any)
}
protocol ViewModelContainer:class,ViewModelContainerVC
{
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
extension ViewModelContainer
{
mutating func setModel(_ model:Any)
{ if model is ViewModelType { viewModel = model as! ViewModelType } }
}
然后,可以使用ViewModelContainerVC进行类型转换和分配:
if let container = container as? ViewModelContainerVC
{
container.setModel(model)
}
[编辑]为了便于将来参考,下面是与Bool返回相同的内容,以实现类型兼容性:
protocol ViewModelContainerVC
{
@discardableResult mutating func setModel(_ :Any) -> Bool
}
extension ViewModelContainer
{
@discardableResult mutating func setModel(_ model:Any) -> Bool
{
if let validModel = model as? ViewModelType
{ viewModel = validModel; return true }
return false
}
}
这将允许组合条件:
if var container = container as? ViewModelContainerVC,
container.setModel(model)
{ ... }
这真是太愚蠢了。。。但如果创建一个空协议,然后使所有符合
ViewModelContainer
的类型都符合该协议,然后检查是否符合该协议,而不会碰到此问题。我想真正的问题是为什么container
不是静态类型,作为符合ViewModelContainer
的类型?你可能正在寻找一个类型擦除器。为什么不把ViewModelType
作为一个协议呢?@Hamish-UIKit讨厌泛型,我不得不在某个时候使用Any
来解决这个问题it@dichen我已经有了ViewModel
协议。但是它在这里没有帮助,因为每个容器都必须包含其特定的视图模型类。嗯,我更喜欢在prepare
上详细一点,而不是在视图模型和丢失类型安全性上详细一点。这是一个很好的解决方法。但是为什么要使用is
和as
而不是如果让或直接作为?
没有特别的原因,我只是觉得它更好地传达了意图。这似乎非常不快速:当使用错误的类型调用setModel
方法时,它会自动失败,但编译器没有帮助。您可以使用任何类型的参数在任何ViewModelContainer
上调用它。您始终可以在协议中添加另一个方法来验证模型的类型。e、 g.func matchesModel(model)->Bool或使setModel函数本身返回Bool结果。如果模型有效,我最终使用此解决方案返回true
。我喜欢视图模型和控制器都没有被污染,所有的样板文件都保存在扩展方法中。谢谢