代码之家  ›  专栏  ›  技术社区  ›  iOSGeek

具有两个不带公共父级的uiviewcontroller的依赖关系图的swift依赖注入

  •  8
  • iOSGeek  · 技术社区  · 7 年前

    当我们有两个在层次结构中非常深的uiviewController,并且它们都需要保持状态的相同依赖项,并且这两个uiviewController没有公共父级时,我们如何在不使用框架的情况下应用依赖项注入。

    例子:

    VC1->VC2->VC3->VC4

    VC5->VC6->VC7->VC8

    让我们坐上他们都需要的VC4和VC8 UserService 它保存当前用户。

    注意我们要避免单件。

    有没有一种优雅的方法来处理这种DI情况?

    经过研究,我发现有人提到 Abstract Factory , Context interfaces , Builder ,请 strategy pattern

    但是我找不到一个如何在iOS上应用的例子

    7 回复  |  直到 7 年前
        1
  •  6
  •   Gero    7 年前

    好吧,我试试看。

    你说“不要单件”,所以我在下面排除了这一点,但请看下面这个答案。

    乔希·霍曼的评论已经是一个很好的解决方案的指针,但我个人对协调模式有一些问题。

    正如乔希正确地说的那样,视图控制器不应该(太多)了解彼此[1],但是,例如,如何传递/访问协调器或任何依赖项?有几种模式可以建议如何进行,但大多数模式都有一个基本上违背您要求的问题:它们或多或少地使协调器成为一个单例(或者它本身,或者作为另一个单例的属性,比如 AppDelegate )。协调者也常常被认为是一个单独的个体(但并非总是如此,而且不一定如此)。

    我倾向于依靠 简单初始化属性或 (最常) 惰性性质 以及面向协议的编程。让我们构造一个示例: UserService 应是定义您服务所需的所有功能的协议, MyUserService 它的实现结构。让我们假设 用户服务 是一种设计结构,基本上用作某些用户相关数据的getter/setter系统:访问令牌(例如保存在keychain中)、某些首选项(avatar图像的URL)等。初始化时 我的用户服务 还准备数据(例如从远程加载)。这将在多个独立的屏幕/视图控制器中使用,而不是单个屏幕/视图控制器。

    现在,对访问此数据感兴趣的每个视图控制器都有一个简单的属性:

    lazy var userService: UserService = MyUserService()
    

    我将它公开,因为这样我就可以在单元测试中轻松地模拟/存根它(如果我需要这样做,我可以创建一个虚拟的 TestUserService 嘲笑/扼杀行为)。实例化也可以是一个闭包,如果init需要参数,我可以在测试期间轻松地将其关闭。显然,属性甚至不需要 lazy 取决于对象实际执行的操作。如果提前实例化对象不会造成任何伤害(请记住单元测试以及传出连接),只需跳过 懒惰的 .

    诀窍 显然是 设计 用户服务 和/或 MyService 在创建多个实例时不会导致问题 .但是,我发现这并不是一个真正的问题,90%的时间内,只要实例所依赖的实际数据保存在其他地方,在一个单一的事实点上,比如钥匙链、核心数据栈、用户默认值或远程后端。

    我知道这是一种逃避的回答,在某种程度上,我只是说描述一种方法,它(至少是其中的一部分)是许多通用模式。然而,我发现这是在swift中处理依赖注入的最通用和最简单的形式。协调模式可以与之正交使用,但我发现它在日常使用中不太像苹果。它确实解决了一个问题,但大多数情况下,你得到的故事板并没有按预期正确地使用(特别是:仅仅将它们用作“vc repos”,从那里实例化它们,然后在代码中转换自己)。

    [1]除了一些基本和/或次要的事情,您可以在完成处理程序中传递,或者 prepareForSegue . 这是有争议的,取决于你对协调人或其他模式的严格程度。就我个人而言,我有时会在这里走捷径,只要它不会使事情膨胀,变得凌乱。一些弹出式设计就是这样简单地完成的。


    作为结束语,“注意,我们要避免单件事”这句话以及你对问题下的评论给我的印象是,你只是按照建议行事,而没有适当考虑理由。我知道“单身”经常被认为是一种反模式,但同样的,判断也经常被误传。单例可以是一个有效的体系结构概念(您可以看到,它在框架和库中被广泛使用)。它的坏处在于它经常诱惑开发人员在设计中使用快捷方式,并将其滥用为一种“对象存储库”,这样他们就不需要考虑何时何地实例化对象。这会导致混乱和模式的坏名声。

    用户服务 ,取决于你的应用程序中的实际功能 可以 做单身汉的好候选人。我个人的经验法则是:“如果它管理着某个独特的事物的状态,比如某个特定的用户在给定的时间只能处于一种状态”,我 可以 去单打吧。

    尤其是如果你不能按照我上面概述的方式来设计它,也就是说。 如果您需要在内存中存储,那么就需要单一的状态数据 一个单件基本上是一个简单的和 适当的 实现这一点的方法。(即使使用(懒惰的)属性也是有益的,那么视图控制器甚至不需要知道它是否是单例的,您仍然可以单独地进行存根/模拟(即,不仅仅是全局实例)。

        2
  •  3
  •   Mike Taverne    7 年前

    这些是我理解的您的要求:

    1. VC4和VC8必须能够通过 UserService 班级。
    2. 用户服务 不能是单身汉。
    3. 用户服务 必须使用依赖注入向VC4和VC8提供。
    4. 不能使用依赖项注入框架。

    在这些限制条件下,我建议采用以下方法。

    定义一个 UserServiceProtocol 具有用于访问和更新状态的方法和/或属性的。例如:

    protocol UserServiceProtocol {
        func login(user: String, password: String) -> Bool
        func logout()
        var loggedInUser: User? //where User is some model you define
    }
    

    定义一个 用户服务 实现协议并将其状态存储在某个位置的类。

    如果状态只需要持续应用程序运行的时间,则可以将状态存储在特定的 实例 但是这个 实例 必须在VC4和VC8之间共享。

    在这种情况下,我建议在 AppDelegate 通过VCS链。

    如果在应用程序启动之间需要保持状态,或者如果不想通过VCS链传递实例,则可以将状态存储在用户默认值、核心数据、领域或类本身外部的任何数量的地方。

    在这种情况下,您可以创建 用户服务 在VC3和VC7中,并将其传递给VC4和VC8。VC4和VC8应该 var userService: UserServiceProtocol? .这个 用户服务 需要从外部源恢复其状态。这样,即使VC4和VC8具有不同的对象实例,状态也会相同。

        3
  •  2
  •   user3581248    7 年前

    首先,我认为你的问题有一个错误的假设。

    您可以这样定义VC的层次结构:

    例子:

    VC1->VC2->VC3->VC4

    VC5->VC6->VC7->VC8

    然而,在iOS上(除非你使用一些非常奇怪的黑客),总有一个共同的家长在某个点上,像导航控制器,标签栏控制器,主细节控制器或页面视图控制器。

    所以我假设一个正确的方案可以看起来像这样:

    选项卡栏控制器1->导航控制器1->VC1->VC2->VC3->VC4

    选项卡栏控制器1->导航控制器2->VC5->VC6->VC7->VC8

    我相信像这样看着它可以很容易地回答你的问题。

    现在,如果你在征求意见,在iOS上处理DI的最佳方法是什么,我会说没有最好的方法。然而,我个人喜欢坚持这样一条规则:对象不应该为自己的创建/初始化负责。这样的事情

    private lazy var service: SomeService = SomeService()
    

    毫无疑问。我想要一个需要 SomeService 实例或至少(对视图控制器来说很容易):

    var service: SomeService!
    

    这样,您就可以将获取正确模型/服务等的责任传递给实例的创建者,同时您可以使用一个简单但重要的假设来实现您的逻辑,即您拥有所需的一切(或者使类提前失败(例如使用强制解包),这在开发期间实际上是很好的。NT)。

    现在,如何获取这些模型——是通过初始化它们、传递它们、拥有一个单独的、使用提供者、容器、协调器等来获取的——这完全取决于您自己,还应该取决于项目的复杂性、客户的需求、您使用的任何工具——所以一般来说,只要您坚持下去,任何工作都是好的。良好的OOP实践。

        4
  •  2
  •   Markk    7 年前

    下面是我在一些项目中使用的一种方法,可以帮助您。

    1. 通过ViewControllerFactory中的工厂方法创建所有视图控制器。
    2. ViewControllerFactory有自己的UserService对象。
    3. 将ViewControllerFactory的UserService对象传递给需要它的视图控制器。

    这里有一个简单的例子:

    struct ViewControllerFactory {
    
    private let userService: UserServiceProtocol
    
    init(userService: UserServiceProtocol) {
        self.userService = userService
    }
    
    // This VC needs the user service
    func makeVC4() -> VC4 {
        let vc4 = VC4(userService: userService)
        return vc4
    }
    
    // This VC does not
    func makeVC5() -> VC5 {
        let vc5 = VC5()
    }
    
    // This VC also needs the user service
    func makeVC8() -> VC8 {
        let vc8 = VC8(userService: userService)
        return vc8
    }
    }  
    

    ViewControllerFactory对象可以实例化并存储在AppDelegate中。

    这是最基本的。此外,我还将查看以下内容(另请参阅此处提出了一些好建议的其他答案):

    1. 创建UserService符合的UserServiceProtocol。这使得为测试创建模拟对象变得容易。
    2. 查看协调模式以处理导航逻辑。
        5
  •  0
  •   andrei    7 年前

    我发现协调器/路由器设计模式最适合注入依赖项和处理应用程序导航。看看这篇文章,它对我有很大帮助 https://medium.com/@dkw5877/flow-coordinators-333ed64f3dd

        6
  •  0
  •   Ivan S Ivanov    7 年前

    我试图解决这个问题,并在这里上传了一个架构示例: https://github.com/ivanovi/DI-demo

    为了更清楚地说明这一点,我使用三个VCS简化了实现过程,但该解决方案可以在任何深度下工作。视图控制器链如下:

    master->详细信息->更多详细信息(插入依赖项的位置)

    提议的架构有四个构建块:

    • 协调器存储库:包含所有协调器和共享状态。注入所需的依赖项。

    • 视图控制器协调器:执行到下一个视图控制器的导航。协调器持有一个工厂,该工厂生成所需的VC下一个实例。

    • viewcontroller工厂:负责初始化和配置特定的viewcontroller。它通常由协调员所有,由协调员报告注入协调员。

    • 视图控制器:屏幕上显示的视图控制器。

    注意:在这个例子中,我返回新创建的vc实例只是为了生成这个例子——也就是说,在实际的实现中,不需要返回vc。

    希望它有帮助。

        7
  •  0
  •   Sachin S    7 年前
    let viewController = CustomViewController()
    viewController.data = NSObject() //some data object
    navigationController.show(viewController, sender: self)
    
    
    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
        var appCoordinator:AppCoordinator?
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            // Override point for customization after application launch.
            window = UIWindow(frame: UIScreen.main.bounds)
            window?.rootViewController = UINavigationController()
            appCoordinator = AppCoordinator(with: window?.rootViewController as! UINavigationController)
            appCoordinator?.start()
            window?.makeKeyAndVisible()
            return true
        }
    }