【问题标题】:Suggestions for dependency injection to view controllers in swift依赖注入快速查看控制器的建议
【发布时间】:2019-07-05 19:47:58
【问题描述】:

我正在使用 Swift 制作一个 iOS 应用程序,该应用程序对于组成该应用程序的大多数视图控制器具有不同的状态。视图控制器依赖的少数“状态”是用户是否登录,或者地址是否已注册、搜索或丢失等。目前,数据在@中提供给视图控制器987654330@ 方法。

以下是我的应用程序的结构,还有大约十几个具有类似结构的视图控制器。

App.swift

struct App {
  enum LoginState {
    case unregistered
    case registered(User) // User defined elsewhere
  }

  enum OtherState {
    case stateOne
    case stateTwo(AssociatedType)
    case stateThree(OtherAssociatedType)
  }

  // Default states
  var loginState: LoginState = .unregistered
  var otherState: OtherState = .stateOne
}

HomeViewController.swift

class HomeViewController: UIViewController {
  var app: App!

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    switch segue.identifier {
    case "Other View Controller Segue":
      let otherVC = segue.destination as! OtherViewController
      otherVC.app = app

    default:
      break
    }
}

AppDelegate.swift

class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let storybard = UIStoryboard(name: "Main", bundle: nil)
    let mainNavController = storyboard.instantiateViewController(withIdentifier: "Main Navigation Controller") as! UINavigationController
    let homeViewController = mainNavController.topViewController as! HomeViewController

    window?.rootViewController = mainNavController
    // 'injecting' app to the homeViewController
    homeViewController.app = App()

    return true
}

大约一年前第一次遇到“如何在视图控制器之间传递数据”时,我找不到原始帖子,但这可能是其中之一:Passing Data between View Controllers(其中没有提到任何地方的术语依赖注入)。 然后我大约一个月前听到了依赖注入这个术语,这个花哨的术语似乎很适合我处理需要处理数据的类(HomeViewControllerOtherViewController)州 (struct App)。

为了验证这种在​​ segue 中抛出 app 实例的方法,我做了几个晚上的研究。 但是现在我已经被太多的信息淹没了:several dependency injection methodologiesunit testing in swift via DI、一些 DI 框架hereherehereand here、单元测试框架herehere , 和(现在由于缺乏声誉,我无法添加更多链接..),以及一大堆媒体/博客/SO 帖子。

问题是:1. 我是不是在这里做所谓的依赖注入,这是“正确的方式”吗? 2. 我可以使用这种方法进行单元/UI 测试? 3. 如果我正在做的是合法的依赖注入,为什么需要依赖注入框架?

依赖注入框架看起来没有必要,神秘,(而且很吓人),至少对我来说只是试图编写一个遵循 SOLID 原则的可维护应用程序。 我厌倦了抽象概念或与我的情况看似无关的示例,所以如果有人帮助我解决这些概念和设计决策,包括对框架的需求,我会很高兴。

【问题讨论】:

    标签: ios swift dependency-injection


    【解决方案1】:

    我建议避免关注不同类型 DI 的技术细节,我会关注基本概念。 DI 意味着你的视图控制器自己没有得到它需要的依赖,但是依赖是从外部提供的。 所以你的otherVC.app = app 是一个非常小的 DI 基本示例。

    简而言之,您现在可以通过 app 的不同实例来测试您的视图控制器,这是执行 DI 的主要目标。

    你能做得更好吗?我们可能会。例如,让您的 App 对象更小一点,将“状态”分成更小的部分。 另一个改进可能是在视图控制器中使用协议而不是具体类型。这样,在测试过程中,您可以传递一个模拟对象作为依赖项,这对您的测试有很大帮助。

    【讨论】:

    • 太棒了。所以实现一个像LoginService 这样的协议,并使App 结构符合它,可能在将它分成更小的部分之后?另外,我目前正在大约几十个VC中的prepare(for:sender)方法中初始化app,有没有办法解决这种冗余?
    • 经过更多研究,introducing-perform-easy-dependency-injection-for-storyboard-segues 发布关于框架 Perform 的帖子似乎解决了我最后的问题。框架减少了样板代码以将依赖项交给其他类,对吗?
    【解决方案2】:

    我同意IgnazioC's answer 并想补充一些东西。

    我认为您可以问您这些问题,以了解您的实施是否是 DI 的一个很好的例子。

    • 我可以获取我的任何消费者(viewController)并使用我从外部设置的新依赖项运行它。如果答案是肯定的。这是 DI 的好习惯。
    • 我可以多么容易地做到这一点,我可以多么容易地编写所需的代码来使用它。这个答案决定了你的练习有多好。

    您的代码解决了您的问题。但我认为(也许我错了)这会产生另一个问题。

    • 从另一个 viewController 决定一个 viewController 的依赖关系是个好主意吗?真的,这是 viewController 的工作吗?或者应该是?在大多数情况下,我认为这不是一个好习惯。

    • 1234563这会创建一些复制代码。也许其中一些可以通过复合模式或类似的东西来管理,但不是全部。

    我认为(也许我又错了)在消费者之间携带依赖关系不是一个好主意。至少大部分是。也许你会写一个足够小的应用程序来解决这个问题。但大多数情况下,依赖关系应该在 viewController 链之外解决。

    注意:我不包括从以前的控制器创建的依赖项,就像在主细节场景中发生的那样。它们是不同的东西。

    在 swift 中,目前这有点困难。也许 swift 需要一些改进才能更容易地做到这一点(比如更好的反射器)。但是,我发现an atricle 关于 Swift 5.1 中的 DI 使用情况。我还没用这个。但这似乎是一个有用的解决方案。也许这可以帮助你。

    【讨论】:

    • 它在包装器中使用了一个名为 Resolver 的框架,它又回到了我的问题:为什么要使用一个?这似乎有点矫枉过正。
    • 这只是我提到的DI实现的一个例子。事实上,您不需要使用 Resolver framewok。自己制作是非常基本的。你可以在 github 上查看他们的代码。他们使用非常简单和通用的方式来执行 DI。它给了你非常重要的东西。 Single Responsibility。正如我之前所说,决定如何创建消费者的依赖项不应该是另一个消费者的工作。使用带有包装器的解析器是关于 swift 的新更新。解析器的贡献者将来可能会将此包装器添加到他们的代码中。
    • 现在我掌握了它。谢谢。