• 依赖注入(Dependency Injection, DI)在 iOS 开发中的应用


    iOS 开发中,我们经常会遇到类与类之间存在依赖关系的情况。例如,一个视图控制器可能需要一个服务对象来处理数据,这种情况下,视图控制器就依赖于这个服务对象。传统的做法是直接在视图控制器中创建服务对象,但这会导致类之间的紧密耦合,降低代码的可维护性和可测试性。为了改善这一点,我们可以使用依赖注入(Dependency Injection, DI)模式。

    在这里插入图片描述

    入门

    什么是依赖注入?

    依赖注入是一种设计模式,用于解除对象之间的依赖关系。通过依赖注入,一个类所依赖的对象(即依赖)由外部传递给它,而不是在类内部自己创建。这样可以降低类之间的耦合度,提高代码的可维护性和可测试性。

    依赖注入的类型

    依赖注入主要有三种类型:构造函数注入、属性注入和方法注入。

    1. 构造函数注入

    构造函数注入是通过构造函数将依赖对象传递给类。构造函数注入通常是最常用和推荐的方式,因为依赖在对象创建时就被注入,从而确保了对象的完整性。

    class AuthService {
        private let userManager: UserManagerProtocol
    
        init(userManager: UserManagerProtocol) {
            self.userManager = userManager
        }
    
        func authenticate() {
            guard let user = userManager.currentUser else {
                print("No user to authenticate")
                return
            }
            print("Authenticating user: \(user.name)")
        }
    }
    
    2. 属性注入

    属性注入是通过设置类的属性将依赖对象传递给类。属性注入允许在对象创建之后再注入依赖,适用于那些在对象创建时不需要立即使用依赖的情况。

    class AuthService {
        var userManager: UserManagerProtocol?
    
        func authenticate() {
            guard let userManager = userManager, let user = userManager.currentUser else {
                print("No user to authenticate")
                return
            }
            print("Authenticating user: \(user.name)")
        }
    }
    
    // 使用时
    let authService = AuthService()
    authService.userManager = UserManager.shared
    authService.authenticate()
    
    3. 方法注入

    方法注入是通过方法参数将依赖对象传递给类。方法注入适用于那些只在特定方法调用时才需要依赖的情况。

    class AuthService {
        func authenticate(userManager: UserManagerProtocol) {
            guard let user = userManager.currentUser else {
                print("No user to authenticate")
                return
            }
            print("Authenticating user: \(user.name)")
        }
    }
    
    // 使用时
    let authService = AuthService()
    authService.authenticate(userManager: UserManager.shared)
    

    依赖注入的好处

    1. 提高代码的可测试性:通过依赖注入,可以轻松地替换依赖对象,从而进行单元测试。例如,可以在测试中注入一个模拟对象(mock object)来验证依赖类的行为。

    2. 减少类之间的耦合:依赖注入使类只依赖于接口或抽象类,而不是具体实现,从而降低了类之间的耦合度。

    3. 提高代码的灵活性和复用性:通过依赖注入,可以轻松地替换依赖对象,从而实现不同的功能或行为。例如,可以注入不同的实现来实现不同的策略模式。

    使用依赖注入框架

    在实际开发中,我们可以使用依赖注入框架来管理和注入依赖对象。Swinject 是一个流行的 Swift 语言依赖注入框架,可以帮助我们轻松实现依赖注入。

    Swinject 示例

    以下是一个使用 Swinject 的简单示例:

    import Swinject
    
    // 定义协议
    protocol UserManagerProtocol {
        var currentUser: UserProtocol? { get }
    }
    
    // 定义实现类
    class UserManager: UserManagerProtocol {
        static let shared = UserManager()
        var currentUser: UserProtocol?
    
        private init() {}
    }
    
    protocol AuthServiceProtocol {
        func authenticate()
    }
    
    class AuthService: AuthServiceProtocol {
        private let userManager: UserManagerProtocol
    
        init(userManager: UserManagerProtocol) {
            self.userManager = userManager
        }
    
        func authenticate() {
            guard let user = userManager.currentUser else {
                print("No user to authenticate")
                return
            }
            print("Authenticating user: \(user.name)")
        }
    }
    
    // 配置 Swinject 容器
    let container = Container()
    container.register(UserManagerProtocol.self) { _ in UserManager.shared }
    container.register(AuthServiceProtocol.self) { r in
        AuthService(userManager: r.resolve(UserManagerProtocol.self)!)
    }
    
    // 使用依赖注入
    let authService = container.resolve(AuthServiceProtocol.self)!
    authService.authenticate()
    

    在这个示例中,我们通过 Swinject 框架配置了依赖注入容器,将 UserManagerAuthService 注册到容器中,并在需要使用时解析依赖。

    小结

    依赖注入是一种强大的设计模式,可以显著提高代码的可维护性和可测试性。通过引入依赖注入,我们可以解除对象之间的紧密耦合,使代码更加灵活和可扩展。希望本文对你理解和应用依赖注入有所帮助,并能在实际开发中充分利用这一模式来改善你的代码质量。


    对 Swinject 深入了解

    上文“配置 Swinject 容器”和“使用依赖注入”部分的代码,对于新手来说可能并不友好,如下是对其详细的解释和分析

    配置 Swinject 容器

    首先,我们需要配置一个 Swinject 容器,用于管理依赖关系。容器会存储对象及其依赖关系,并在需要时提供这些对象。

    import Swinject
    
    // 配置 Swinject 容器
    let container = Container()
    
    // 注册 UserManagerProtocol 类型
    container.register(UserManagerProtocol.self) { _ in
        UserManager.shared
    }
    
    // 注册 AuthServiceProtocol 类型
    container.register(AuthServiceProtocol.self) { resolver in
        AuthService(userManager: resolver.resolve(UserManagerProtocol.self)!)
    }
    
    代码解释
    1. 创建容器let container = Container() 创建了一个 Swinject 容器。

    2. 注册 UserManagerProtocolcontainer.register(UserManagerProtocol.self) { _ in UserManager.shared } 向容器注册 UserManagerProtocol 类型。当需要 UserManagerProtocol 的实例时,容器会提供 UserManager.shared 实例。

    3. 注册 AuthServiceProtocolcontainer.register(AuthServiceProtocol.self) { resolver in AuthService(userManager: resolver.resolve(UserManagerProtocol.self)!) } 向容器注册 AuthServiceProtocol 类型。当需要 AuthServiceProtocol 的实例时,容器会创建一个 AuthService 实例,并使用 resolver.resolve(UserManagerProtocol.self)! 获取 UserManagerProtocol 的实例作为其依赖。

    使用依赖注入

    配置好容器后,我们可以从容器中解析(获取)依赖对象,并使用它们。

    // 使用依赖注入
    let authService = container.resolve(AuthServiceProtocol.self)!
    authService.authenticate()
    
    代码解释
    1. 解析 AuthServiceProtocollet authService = container.resolve(AuthServiceProtocol.self)! 从容器中解析 AuthServiceProtocol 的实例。容器会根据之前的注册信息,提供一个 AuthService 实例。

    2. 使用 AuthServiceauthService.authenticate() 调用 AuthServiceauthenticate 方法。

    完整示例

    以下是完整的代码示例,展示如何配置 Swinject 容器和使用依赖注入:

    import Swinject
    
    // 定义协议
    protocol UserManagerProtocol {
        var currentUser: UserProtocol? { get }
    }
    
    // 定义实现类
    class UserManager: UserManagerProtocol {
        static let shared = UserManager()
        var currentUser: UserProtocol?
    
        private init() {}
    }
    
    protocol AuthServiceProtocol {
        func authenticate()
    }
    
    class AuthService: AuthServiceProtocol {
        private let userManager: UserManagerProtocol
    
        init(userManager: UserManagerProtocol) {
            self.userManager = userManager
        }
    
        func authenticate() {
            guard let user = userManager.currentUser else {
                print("No user to authenticate")
                return
            }
            print("Authenticating user: \(user.name)")
        }
    }
    
    // 配置 Swinject 容器
    let container = Container()
    
    // 注册 UserManagerProtocol 类型
    container.register(UserManagerProtocol.self) { _ in
        UserManager.shared
    }
    
    // 注册 AuthServiceProtocol 类型
    container.register(AuthServiceProtocol.self) { resolver in
        AuthService(userManager: resolver.resolve(UserManagerProtocol.self)!)
    }
    
    // 使用依赖注入
    let authService = container.resolve(AuthServiceProtocol.self)!
    authService.authenticate()
    

    小结

    通过 Swinject 容器,我们可以轻松地管理对象及其依赖关系,并在需要时解析这些对象。这种方式可以解除类之间的紧密耦合,使代码更具可维护性和可测试性。如果你有任何进一步的问题,请随时提出!


    俯瞰 DI 在组件化项目中的位置

    在这里插入图片描述

    下面是完整的代码示例,明确注明每部分代码应放在哪个组件中。

    CommonModule

    UserProtocol.swift
    public protocol UserProtocol {
        var name: String { get }
    }
    
    public protocol UserManagerProtocol {
        var currentUser: UserProtocol? { get }
    }
    
    public protocol AuthServiceProtocol {
        func authenticate()
    }
    

    AuthComponent

    AuthService.swift
    import CommonModule
    
    public class AuthService: AuthServiceProtocol {
        private let userManager: UserManagerProtocol
    
        public init(userManager: UserManagerProtocol) {
            self.userManager = userManager
        }
    
        public func authenticate() {
            guard let user = userManager.currentUser else {
                print("No user to authenticate")
                return
            }
            print("Authenticating user: \(user.name)")
        }
    }
    
    AuthComponent.podspec
    Pod::Spec.new do |s|
      s.name         = 'AuthComponent'
      s.version      = '1.0.0'
      s.summary      = 'A short description of AuthComponent.'
      s.description  = <<-DESC
                       A longer description of AuthComponent.
                       DESC
      s.homepage     = 'https://example.com/AuthComponent'
      s.license      = { :type => 'MIT', :file => 'LICENSE' }
      s.author       = { 'Your Name' => 'you@example.com' }
      s.source       = { :git => 'https://github.com/yourname/AuthComponent.git', :tag => s.version.to_s }
      s.ios.deployment_target = '10.0'
      s.source_files  = 'Sources/**/*.{h,m,swift}'
    
      # 依赖 CommonModule
      s.dependency 'CommonModule', '~> 1.0.0'
    end
    

    UserComponent

    UserManager.swift
    import CommonModule
    
    public class UserManager: UserManagerProtocol {
        public static let shared = UserManager()
        public var currentUser: UserProtocol?
    
        private init() {}
    }
    
    UserComponent.podspec
    Pod::Spec.new do |s|
      s.name         = 'UserComponent'
      s.version      = '1.0.0'
      s.summary      = 'A short description of UserComponent.'
      s.description  = <<-DESC
                       A longer description of UserComponent.
                       DESC
      s.homepage     = 'https://example.com/UserComponent'
      s.license      = { :type => 'MIT', :file => 'LICENSE' }
      s.author       = { 'Your Name' => 'you@example.com' }
      s.source       = { :git => 'https://github.com/yourname/UserComponent.git', :tag => s.version.to_s }
      s.ios.deployment_target = '10.0'
      s.source_files  = 'Sources/**/*.{h,m,swift}'
    
      # 依赖 CommonModule 和 AuthComponent
      s.dependency 'CommonModule', '~> 1.0.0'
      s.dependency 'AuthComponent', '~> 1.0.0'
    end
    

    Example Project

    Podfile
    platform :ios, '10.0'
    
    target 'ExampleApp' do
      use_frameworks!
    
      # 使用 UserComponent 和 AuthComponent
      pod 'UserComponent', :path => '../UserComponent'
      pod 'AuthComponent', :path => '../AuthComponent'
    end
    
    main.swift (主工程的任意合适位置)
    import Swinject
    import CommonModule
    import UserComponent
    import AuthComponent
    
    // 配置 Swinject 容器
    let container = Container()
    
    // 注册 UserManagerProtocol 类型
    container.register(UserManagerProtocol.self) { _ in
        UserManager.shared
    }
    
    // 注册 AuthServiceProtocol 类型
    container.register(AuthServiceProtocol.self) { resolver in
        AuthService(userManager: resolver.resolve(UserManagerProtocol.self)!)
    }
    
    // 使用依赖注入
    let authService = container.resolve(AuthServiceProtocol.self)!
    authService.authenticate()
    

    项目结构

    CommonModule/
    ├── UserProtocol.swift
    
    AuthComponent/
    ├── AuthComponent.podspec
    ├── Sources/
    │   └── AuthService.swift
    
    UserComponent/
    ├── UserComponent.podspec
    ├── Sources/
    │   └── UserManager.swift
    
    ExampleApp/
    ├── Podfile
    ├── main.swift
    

    说明

    1. CommonModule:定义了协议 UserProtocolUserManagerProtocolAuthServiceProtocol
    2. AuthComponent:实现了 AuthService,并在 podspec 文件中声明了对 CommonModule 的依赖。
    3. UserComponent:实现了 UserManager,并在 podspec 文件中声明了对 CommonModuleAuthComponent 的依赖。
    4. Example Project:使用 PodfileUserComponentAuthComponent 加入工程,并配置 Swinject 容器来注入依赖。

    通过这些配置和组织,我们能够实现依赖注入,从而使代码更加模块化和可维护。

  • 相关阅读:
    信息控制信息卷积
    vue-router(基本使用、路由重定向、多级路由、路由命名、路由的query和params参数、路由的props配置)(十一)
    UE4 回合游戏项目 20- 添加人物被攻击的动画
    记录在模拟器中运行flutter时报的错
    【LeetCode刷题-滑动窗口】--992.K个不同整数的子数组
    305_C++_定义了一个定时器池 TimerPool 类和相关的枚举类型和结构体
    JavaWeb的Servlet项目导入Eclipse以及配置Mysql等通用教程
    Vue3实现获取验证码按钮倒计时效果
    12:面试题:react,vue中的key有什么作用?(key的内部原理)
    Win11切换输入法ctrl+shift没有反应怎么解决?
  • 原文地址:https://blog.csdn.net/qfeung/article/details/139869771