【问题标题】:Weird retain cycle when using the Coordinator pattern使用协调器模式时奇怪的保留周期
【发布时间】:2019-11-28 10:29:14
【问题描述】:

我正在使用 MVVM + Coordinators 构建一个新应用程序。具体来说,我使用的是 https://github.com/daveneff/Coordinator 上的协调器模式。

在顶层我有一个 AppCoordinator,它可以启动子协调器 RegisterCoordinator。注册流程完成后,AppCoordinator 会切换其导航器的根视图控制器,注册流程中使用的协调器和视图控制器应从内存中释放。

final class AppCoordinator: CoordinatorNavigable {
  var dependencies: AppDependencies
  var childCoordinators: [Coordinator] = []
  var rootViewController = UINavigationController()
  var navigator: NavigatorType

  init(window: UIWindow, dependencies: AppDependencies) {
    self.dependencies = dependencies
    navigator = Navigator(navigationController: rootViewController)

    dependencies.userManager.delegate = self

    window.rootViewController = rootViewController
    window.makeKeyAndVisible()
  }

  func start() {
    if dependencies.properties[.user] == nil {
      // Logged out state
      let vc = AuthFlowViewController.instantiate(storyboardName: Constants.Storyboards.authFlow)
      vc.delegate = self
      navigator.setRootViewController(vc, animated: false)
    } else {
      // Logged in
      let vc = HomeViewController.instantiate(storyboardName: Constants.Storyboards.home)
      vc.viewModel = HomeViewModel(dependencies: dependencies)
      navigator.setRootViewController(vc, animated: false)
    }

    childCoordinators = []
  }
}

extension AppCoordinator: UserManagerDelegate {
  func authStateChanged() {
    // User logged in or logged out; show the correct root view controller
    start()
  }

  func userChanged() {}
}

extension AppCoordinator: AuthFlowViewControllerDelegate {
  func login() {
    dependencies.userManager.changeUser(newUser: User(id: 1, name: "Kevin"))
  }

  func startRegisterFlow() {
    let registerCoordinator = RegisterCoordinator(dependencies: dependencies, navigator: navigator)
    pushCoordinator(registerCoordinator, animated: true)
  }
}

RegisterCoordinator 同时简单地将多个视图控制器推送到导航器的堆栈中:

class RegisterCoordinator: CoordinatorNavigable {
  var dependencies: AppDependencies
  var childCoordinators: [Coordinator] = []
  var navigator: NavigatorType

  let rootViewController = PhoneInputViewController.instantiate(storyboardName: Constants.Storyboards.authFlow)

  init(dependencies: AppDependencies, navigator: NavigatorType) {
    self.dependencies = dependencies
    self.navigator = navigator
    rootViewController.delegate = self
  }

  func start() {}
}

extension RegisterCoordinator: PhoneInputViewControllerDelegate {
  func phoneInputDone() {
    let vc = PhoneValidationViewController.instantiate(storyboardName: Constants.Storyboards.authFlow)
    vc.delegate = self
    navigator.push(vc, animated: true)
  }
}

extension RegisterCoordinator: PhoneValidationViewControllerDelegate {
  func phoneValidationDone() {
    let vc = GenderSelectionViewController.instantiate(storyboardName: Constants.Storyboards.authFlow)
    vc.viewModel = GenderSelectionViewModel(dependencies: dependencies)
    navigator.push(vc, animated: true)
  }
}

当整个注册流程完成后,最后一页可以保存用户,这会触发AppCoordinator中的authStateChanged方法,然后改变导航器的rootViewController。这也应该清理它的子协调器。

但遗憾的是,RegisterCoordinator 及其 rootViewController (PhoneInputViewController) 仍保持活动状态 - 尽管流程中的其他视图控制器已正确释放。

我尝试在 start 方法中手动执行 childCoordinators = [] 以确保 AppCoordinator 没有对 RegisterCoordinator 的强引用,但即使这样也无济于事。

我不知道是什么保留了强引用,导致保留周期/内存泄漏。我有一个超级最小版本的应用程序,除了显示问题的基本要素外,基本上所有内容都被删除了,在 GitHub 上:https://github.com/kevinrenskers/coordinator-problem

【问题讨论】:

  • 另外,请不要发布包含 pod 的示例代码。我很乐意下载您的项目并运行它,但不能在其中包含 pod。
  • 有一个 pod,它已经提交了,你不需要自己做任何事情。我没发现问题?
  • 不确定你的意思。您的项目将无法编译,因为缺少 Defaults 模块。那是豆荚。我安装它不是为了运行你的项目。
  • 抱歉,Skippit.xcworkspace 未正确提交。现在它是,所以你不必安装任何东西。
  • 不行,还是不能编译。我真的很想向您展示您是如何在对象图或仪器中为您找到此泄漏图表的,但它不会发生,抱歉。

标签: ios swift instruments retain-cycle coordinator-pattern


【解决方案1】:

首先,您在 Coordinator.self 第 132 行的一个块中捕获您的协调器:

我使用调试内存图找到了这个:

还有PhoneInputViewController还活着,你可以用同样的方法检查为什么

我无法完全理解您的协调器模式实现是如何工作的,但最好不要为您的控制器保留强引用。

我一直在使用一些实现,其中控制器仅由UINavigationController 的堆栈保留,window 保留UINavigationController

它保证你的控制器在弹出/替换后总是会死掉。

在您的情况下,我将首先尝试使 childCoordinatorsCoordinator 保持对控制器的弱引用。

【讨论】:

  • 我认为真正的原因是导航器中的第 94 行:completions[viewController] = completion。如果我只是删除它,那么当您完成整个注册流程时,保留周期就会消失。但是,如果您只是启动注册流程,然后按后退按钮,那么保留周期就在那里 - 所以问题简单地转移了。不确定为什么在设置根 VC 后没有正确清理 completions 字典。现在正在调查。
  • 啊哈!在导航器中的第 101 行之后添加completions = [:] 可以解决问题。向此 Coordinator 的创建者发送 PR:github.com/daveneff/Coordinator/pull/1
  • @KevinRenskers 我有一个关于正确做法的类似问题。不使用 pod,而是在 UI 引用丢失时尝试弹回 rootVC。 stackoverflow.com/questions/59486627/…
  • 解释得很好。
【解决方案2】:

rkyr 的回答将我推向了正确的方向,我找到了问题的根源,并向我正在使用的原始 Coordinator 库发送了一个带有修复程序的 PR。因此,请查看那里的单行修复:https://github.com/daveneff/Coordinator/pull/1

【讨论】:

    猜你喜欢
    • 2018-11-15
    • 1970-01-01
    • 2016-05-03
    • 2018-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-13
    • 1970-01-01
    相关资源
    最近更新 更多