【问题标题】:How do I get get set to work with protocols?如何开始使用协议?
【发布时间】:2020-04-22 07:07:28
【问题描述】:

在协议中使用get set 时我很困惑。仅使用 get 可以正常工作,但 set 部分不行。

protocol MainViewModelProtocol {
    var localDoor: LocalDoorCoreDataObject { get set }
}

extension MainViewModelProtocol {
    var localDoor: LocalDoorCoreDataObject {
        get { return MainViewModel.instance.localDoor }
        set { localDoor = newValue }
    }
}

final class MainViewModel: MainViewModelProtocol {
    var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
...

然后当我在viewController使用它时

self.mainViewModel.localDoor = $0

但这给了我错误

无法分配给属性:“mainViewModel”是一个只能获取的属性

如何正确设置?

编辑

viewModel 的启动是通过 factory based dependency injection 完成的

protocol MainViewModelInjected {
    var mainViewModel: MainViewModelProtocol { get }
}

extension MainViewModelInjected {
    var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}

【问题讨论】:

  • 我更新了答案 :) 我也试过 MainViewModel.instance.localDoor = newValue 但不能因为它是而且应该是 let

标签: ios swift compiler-errors protocols


【解决方案1】:

这完全取决于您如何为mainViewModel 创建对象。 让我们用您的代码创建一些案例:

import UIKit
typealias LocalDoorCoreDataObject = String

protocol MainViewModelProtocol {
    var localDoor: LocalDoorCoreDataObject { get set }
}

extension MainViewModelProtocol {
    var localDoor: LocalDoorCoreDataObject {
        get { return MainViewModel.instance.localDoor }
        set { localDoor = newValue }
    }
}

final class MainViewModel: MainViewModelProtocol {
    static let instance = MainViewModel()
    var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}

protocol MainViewModelInjected {
    var mainViewModel: MainViewModelProtocol { get }
}

extension MainViewModelInjected {
    var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}

案例一

这里我们创建一个对象并通过getter作为闭包分配对象。 所以,这里 mainViewModel 只有 getter 而不是 setter,即它是一个 get-only 属性。

var mainViewModel: MainViewModelProtocol { MainViewModel.instance }
mainViewModel.localDoor = "assign some thing" // Error: Cannot assign to property: 'mainViewModel' is a get-only property

案例 2

这里我们直接将对象分配给mainViewModelOther。因此,这将是一个正常的属性,您可以更改模型的属性。

var mainViewModelOther: MainViewModelProtocol = MainViewModel.instance
mainViewModelOther.localDoor = "assign some thing"

案例 3

您还可以创建一个class 来保存您的模型对象,并创建您的类的另一个对象。您可以更改模型的属性。

class MyClass {
    var mainViewModel: MainViewModelProtocol = MainViewModel.instance
}

let myClassObj = MyClass()
myClassObj.mainViewModel.localDoor = "assign some thing"

【讨论】:

    【解决方案2】:

    TL;DR

    将您的MainViewModelProtocol 标记为仅限班级(即protocol MainViewModelProtocol: class { ... })以解决问题。

    长答案

    要了解为什么将您的 MainViewModelProtocol 标记为仅类可以解决问题,我们需要退后几步,看看结构和类是如何在内部存储的。

    案例 1:MainViewModelProtocol 是引用类型(即类)

    首先,让我们考虑MainViewModel 是class 的情况:类是引用类型,这意味着通过mainViewModel 属性检索视图模型后,您将拥有指向同一视图的指针存储在视图控制器中的模型。修改引用的类型也会修改视图本身的视图模型(因为它们都指向同一个对象)。举个例子

    /* ... */
    class MainViewModel: MainViewModelProtocol { /* ... */ }
    
    var viewModel = myViewController.mainViewModel
    viewModel.localDoor = /* something */ 
    

    修改在局部变量 viewModel 和视图控制器之间共享的视图模型。这正是你想要的。

    案例 2:MainViewModelProtocol 是值类型(即 struct)

    现在让我们考虑MainViewModel 是否是struct:structs 是值类型,因此通过mainViewModel 计算属性检索视图模型实质上是克隆视图模型。现在您可以在本地随意修改检索到的视图模型,但无法将其分配回您的视图控制器

    /* ... */
    struct MainViewModel: MainViewModelProtocol { /* ... */ }
    
    var viewModel = myViewController.mainViewModel
    viewModel.localDoor = /* something */ 
    

    只是修改存储在 viewModel 变量中的视图模型的本地副本。无法将局部变量分配回 myViewController。

    结论

    我希望这能说明为什么您的模式只适用于引用类型而不适用于值类型。

    现在 Swift 编译器需要保守并考虑这两种情况,因为它不知道是否所有符合 MainViewModelProtocol 的类型都是类或结构(考虑作为库用户可以遵循的库出售的公共协议) .如果您将类约束添加到协议中,您会告诉编译器使用案例 1 中的模式完全没问题——只需获取一个共享实例并对其进行修改——并且不需要 setter 来修改视图模型。

    【讨论】:

      【解决方案3】:

      当编译器说:

      时,无需将 MainViewModelProtocol 标记为仅类

      无法分配给属性:“mainViewModel”是一个只能获取的属性

      它实际上是在抱怨您的视图控制器实现。我假设mainViewModel 是一个计算属性,所以你不能分配它。

      我设法通过以下操场重现了您的错误:

      typealias LocalDoorCoreDataObject = String
      protocol MainViewModelProtocol {
          var localDoor: LocalDoorCoreDataObject { get set }
      }
      
      extension MainViewModelProtocol {
          var localDoor: LocalDoorCoreDataObject {
              get { return MainViewModel.instance.localDoor }
              set { localDoor = newValue }
          }
      }
      
      final class MainViewModel: MainViewModelProtocol {
          static let instance = MainViewModel()
          var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
      }
      
      final class FakeVC {
          var mainViewModel: MainViewModelProtocol {
              MainViewModel.instance
          }
      }
      var viewController = FakeVC()
      viewController.mainViewModel.localDoor = "foo" // Cannot assign to property: 'mainViewModel' is a get-only property
      

      我通过将 FakeVC 实现更改为:

      final class FakeVC {
          var mainViewModel: MainViewModelProtocol = MainViewModel()
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-11-09
        • 1970-01-01
        • 1970-01-01
        • 2021-06-11
        • 1970-01-01
        • 1970-01-01
        • 2020-08-31
        • 1970-01-01
        相关资源
        最近更新 更多