视图控制器的依赖注入
Dependenct 注入介绍
应用程序由许多彼此协作的对象组成。对象通常依赖于其他对象来执行某些任务。当一个对象负责引用它自己的依赖项时,它会导致高度耦合,难以测试和难以更改的代码。
依赖注入是一种软件设计模式,它实现控制的反转以解决依赖关系。注入是将依赖关系传递给将使用它的依赖对象。这允许将客户端的依赖关系与客户端的行为分开,这允许应用程序松散耦合。
不要与上面的定义混淆 - 依赖注入只是意味着给对象赋予实例变量
就这么简单,但它提供了很多好处:
- 更容易测试你的代码(使用单元和 UI 测试等自动化测试)
- 当与面向协议的编程一起使用时,它可以很容易地改变某个类的实现 - 更容易重构
- 它使代码更加模块化和可重用
有三种最常用的方法可以在应用程序中实现依赖注入(DI):
- 初始化器注入
- 属性注入
- 使用第三方 DI 框架(如 Swinject,Cleanse,Dip 或 Typhoon)
有一篇有趣的文章链接到更多关于依赖注入的文章,所以如果你想深入研究 DI 和 Inversion of Control 原则,请查看它。
让我们展示如何将 DI 与 View Controllers 一起使用 - 这是普通 iOS 开发人员的日常任务。
没有 DI 的例子
我们将有两个视图控制器: LoginViewController 和 TimelineViewController 。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,你将看到好处并始终使用依赖注入。