【问题标题】:MVVM - using closures to bind a ViewModel with a DataSource: capture list needed?MVVM - 使用闭包将 ViewModel 与 DataSource 绑定:需要捕获列表吗?
【发布时间】:2018-07-07 20:56:20
【问题描述】:

我有一个viewController 保持对其viewModel 的(强)引用

lazy private var viewModel: ListViewModel = {
   return ListViewModel()
}()

override func viewDidLoad() {
   super.viewDidLoad()

   initViewModel()
}

private func initViewModel() {
   viewModel.onModelChange = { [weak self] () in
      DispatchQueue.main.async {
         self?.tableView.reloadData()
      }
   }

   viewModel.fetchData()
}

Apple 的 Swift 文档日:

如果您将闭包分配给类实例的属性,并且该闭包的主体捕获该实例,也会发生强引用循环。

这个viewController 包含一个viewController,它包含一个捕获viewController 的闭包,所以我知道可能会发生强引用循环。

这些是viewModel 的属性:

var onModelChange: (() -> Void)?

private var model: [MyModel] = [MyModel]() {
    didSet {
        self.onModelChange?()
    }
}

另一方面,这个viewControllerroot 一个,所以在应用程序的生命周期内不会被释放。而且我没有明确 nilify 它的 viewModel 属性。所以:

  • 问题 1:是否真的存在发生强参考周期的风险?我是否应该向闭包提供捕获列表?
  • 问题 2:我将捕获列表设置为 [weak self],但是,由于似乎 viewController 和它的 viewModel 会同时被释放,我想我应该定义 [unowned self] ?如果有疑问,定义[weak self] 会不会很痛苦?

让我们认为现在viewController 是推到root 之上的一个,因此它可能会在应用程序的生命周期内弹出。 viewControllerfetchData()方法是这样的:

func fetchData() {
    dataSource.getData(completion: { (result, error) -> Void in
        if error != nil {
            self.model = [MyModel]()
        } else {
            if let result = result {
                self.model = result
            } else {
                self.model = [MyModel]()
            }
        }
    })
}

其中dataSource 是一个强属性,可以从本地文件(同步任务)或调用服务(异步任务)获取数据。

model 也是一个被观察并调用从viewController 提供的闭包的属性

所以:

  • 问题 3:如果dataSource 正在从本地文件中获取数据。如果同时弹出viewController 会发生什么?在dataSource 完成之前,viewModel 是否仍然存在?在这种情况下,在闭包中提供[weak self] 捕获列表是否正确?
  • 问题4:如果dataSource正在执行异步网络调用,而viewController同时弹出,会和以前一样吗?我的意思是,在dataSource 完成之前,viewModel 是否还活着?

编辑: 场景/问题 5

现在我的viewModel 还要求对象UserSettingsManager 获取其他数据:

func getUserSettings() {
    UserSettingsManager.getInfo { (result, error) in
        if error == nil {
            self.userData = result
            }
        }
    }
}

但是这个UserSettingsManager 不是viewModel 的属性,就像dataSource 一样,它是一个只提供static func 的类。那些static func 被传递为@escaping 的闭包,所以我想在这种情况下也可能发生强引用循环,对吧?还是这种情况与以前的情况不同?我在这里需要哪个捕获列表(如果有的话)?

【问题讨论】:

    标签: ios swift closures weak-references retain-cycle


    【解决方案1】:

    问题 1:是否真的存在强参考周期的风险 发生?我是否应该向闭包提供捕获列表?

    强引用循环发生。问题应该是强参考周期是否重要。在这种情况下可能不会,但无论如何保留弱引用可能是个好主意,以防万一您将来移动控制器。为未来的安全付出的代价很小。

    问题 2:我将捕获列表设置为 [weak self] 但是,因为它 似乎 viewController 及其 viewModel 将被释放相同 时间,我想我应该定义 [unowned self] 代替?可以 如果有疑问,定义[弱自我]会不会受伤?

    [unowned self] 暗示self 在调用闭包时肯定会存在。除非您绝对确定是这种情况,否则您应该坚持使用[weak self]。我的偏好是始终使用weak,因为它不需要太多额外的努力来安全地展开,并且如果您以某种方式错过了可以释放self 的情况,您可以避免崩溃。如果您在闭包中多次使用self,最好将其解包在保护语句中或将代码移动到单独的函数中。

    问题 3:如果 dataSource 从本地获取数据 文件。如果 viewController 同时弹出会发生什么? 在数据源完成之前,viewModel 是否仍然存在?在 在这种情况下,提供 [weak self] 捕获列表是否正确 关闭?

    在您的示例中,您在闭包中有对 self 的强引用,因此您有一个强引用循环,这意味着 ViewController 永远不会被释放。

    无论哪种方式,如果您同步抓取数据,则在从文件中获取数据并且块中的代码完成之前,点击关闭视图控制器将无法完成它的工作。这些必须在主线程被解除阻塞并且关闭控制器的代码可以执行之前完成。

    问题 4:如果 dataSource 正在执行异步网络 调用,同时 viewController 弹出,将是 和以前一样的场景?我的意思是,viewModel 是否还活着 直到数据源完成?

    如果您将其设为弱引用,并且 dataSource 保留在控制器外部,因此它的寿命比控制器长,则在调用闭包时 self 可能为 nil。

    【讨论】:

    • 感谢您的详细回答。我对最后两个问题还有一些疑问:- 问题 3 场景。由于dataSource 没有明确引用传递给其getData 方法的闭包,但它是@escaping 这样的方法,所以是的,我想那里也可能有一个强大的引用周期,但在viewModel 之间还有dataSource,对吧?那么我应该设置[weak self] 捕获列表吗?
    • 问题4场景:作为dataSource执行异步网络调用,它会保留viewModel直到回调?或者viewModel 是否可以在异步任务正在进行时被释放,然后dataSource 在完成并调用闭包时找不到viewModel (self)?那么我应该在这里也有一个[weak self] 捕获列表吗?
    • 在 scearios 3 和 4 中,viewModel 保持对其dataSource 的强烈引用,那么dataSource 是否可以实际使用viewController?考虑到这两种情况,dataSource 执行异步文件解析和异步网络调用。
    猜你喜欢
    • 2018-10-30
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 2013-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-15
    相关资源
    最近更新 更多