视图控制器的依赖注入

Dependenct 注入介绍

应用程序由许多彼此协作的对象组成。对象通常依赖于其他对象来执行某些任务。当一个对象负责引用它自己的依赖项时,它会导致高度耦合,难以测试和难以更改的代码。

依赖注入是一种软件设计模式,它实现控制的反转以解决依赖关系。注入是将依赖关系传递给将使用它的依赖对象。这允许将客户端的依赖关系与客户端的行为分开,这允许应用程序松散耦合。

不要与上面的定义混淆 - 依赖注入只是意味着给对象赋予实例变量

就这么简单,但它提供了很多好处:

  • 更容易测试你的代码(使用单元和 UI 测试等自动化测试)
  • 当与面向协议的编程一起使用时,它可以很容易地改变某个类的实现 - 更容易重构
  • 它使代码更加模块化和可重用

有三种最常用的方法可以在应用程序中实现依赖注入(DI):

  1. 初始化器注入
  2. 属性注入
  3. 使用第三方 DI 框架(如 Swinject,Cleanse,Dip 或 Typhoon)

有一篇有趣的文章链接到更多关于依赖注入的文章,所以如果你想深入研究 DI 和 Inversion of Control 原则,请查看它。

让我们展示如何将 DI 与 View Controllers 一起使用 - 这是普通 iOS 开发人员的日常任务。

没有 DI 的例子

我们将有两个视图控制器: LoginViewControllerTimelineViewController 。LoginViewController 用于登录,成功后,它将切换到 TimelineViewController。两个视图控制器都依赖于 FirebaseNetworkService

LoginViewController

class LoginViewController: UIViewController {

    var networkService = FirebaseNetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

TimelineViewController

class TimelineViewController: UIViewController {

    var networkService = FirebaseNetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func logoutButtonPressed(_ sender: UIButton) {
        networkService.logutCurrentUser()
    }
}

FirebaseNetworkService

class FirebaseNetworkService {

    func loginUser(username: String, passwordHash: String) {
        // Implementation not important for this example
    }
    
    func logutCurrentUser() {
        // Implementation not important for this example
    }
}

这个例子很简单,但我们假设你有 10 个或 15 个不同的视图控制器,其中一些还依赖于 FirebaseNetworkService。在某些时候,你希望使用公司的内部后端服务将 Firebase 更改为后端服务。为此,你必须浏览每个视图控制器并使用 CompanyNetworkService 更改 FirebaseNetworkService。如果 CompanyNetworkService 中的某些方法发生了变化,那么你将需要做很多工作。

单元和 UI 测试不是本示例的范围,但如果你希望将视图控制器与紧密耦合的依赖关系进行单元测试,那么你将很难这样做。

让我们重写这个例子,并将网络服务注入我们的视图控制器。

依赖注入的示例

为了充分利用依赖注入,让我们在协议中定义网络服务的功能。这样,依赖于网络服务的视图控制器甚至不必知道它的实际实现。

protocol NetworkService {
    func loginUser(username: String, passwordHash: String)
    func logutCurrentUser()
}

添加 NetworkService 协议的实现:

class FirebaseNetworkServiceImpl: NetworkService {
    func loginUser(username: String, passwordHash: String) {
        // Firebase implementation
    }
    
    func logutCurrentUser() {
        // Firebase implementation
    }
}

让我们更改 LoginViewController 和 TimelineViewController 以使用新的 NetworkService 协议而不是 FirebaseNetworkService。

LoginViewController

class LoginViewController: UIViewController {

    // No need to initialize it here since an implementation
    // of the NetworkService protocol will be injected
    var networkService: NetworkService?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

TimelineViewController

class TimelineViewController: UIViewController {

    var networkService: NetworkService?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func logoutButtonPressed(_ sender: UIButton) {
        networkService?.logutCurrentUser()
    }
}

现在,问题是:我们如何在 LoginViewController 和 TimelineViewController 中注入正确的 NetworkService 实现?

由于 LoginViewController 是起始视图控制器,并且每次应用程序启动时都会显示,因此我们可以在 AppDelegate 中注入所有依赖项。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // This logic will be different based on your project's structure or whether
    // you have a navigation controller or tab bar controller for your starting view controller
    if let loginVC = window?.rootViewController as? LoginViewController {
        loginVC.networkService = FirebaseNetworkServiceImpl()
    }
    return true
}

在 AppDelegate 中,我们只是引用第一个视图控制器(LoginViewController)并使用属性注入方法注入 NetworkService 实现。

现在,下一个任务是在 TimelineViewController 中注入 NetworkService 实现。最简单的方法是在 LoginViewController 转换到 TimlineViewController 时执行此操作。

我们将在 LoginViewController 中的 prepareForSegue 方法中添加注入代码(如果你使用不同的方法来浏览视图控制器,请将注入代码放在那里)。

我们的 LoginViewController 类现在看起来像这样:

class LoginViewController: UIViewController {
    // No need to initialize it here since an implementation
    // of the NetworkService protocol will be injected
    var networkService: NetworkService?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "TimelineViewController" {
            if let timelineVC = segue.destination as? TimelineViewController {
                // Injecting the NetworkService implementation
                timelineVC.networkService = networkService
            }
        }
    }
}

我们完成了,就这么简单

现在想象一下,我们希望将 NetworkService 实现从 Firebase 切换到我们自定义公司的后端实现。我们所要做的就是:

添加新的 NetworkService 实现类:

class CompanyNetworkServiceImpl: NetworkService {
    func loginUser(username: String, passwordHash: String) {
        // Company API implementation
    }
    
    func logutCurrentUser() {
        // Company API implementation
    }
}

使用 AppDelegate 中的新实现切换 FirebaseNetworkServiceImpl:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // This logic will be different based on your project's structure or whether
        // you have a navigation controller or tab bar controller for your starting view controller
        if let loginVC = window?.rootViewController as? LoginViewController {
            loginVC.networkService = CompanyNetworkServiceImpl()
        }
        return true
    }

就是这样,我们已经切换了整个网络服务协议的底层实现,甚至没有触及 LoginViewController 或 TimelineViewController。

由于这是一个简单的示例,你现在可能看不到所有好处,但如果你尝试在项目中使用 DI,你将看到好处并始终使用依赖注入。