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

news2024/11/23 8:27:20

在 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 容器来注入依赖。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1847516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

目标跟踪算法(bytetrack)-tensorrt部署教程

一、本机安装python环境 conda create -n bytetrace_env python=3.8 activate bytetrace_env conda install pytorch torchvision cudatoolkit=10.1 -c检测GPU是否可用,不可用不行 import torch print(torch.cuda.is_available())安装bytetrack git clone https://github.c…

车辆轨迹预测系列 (二):常见数据集介绍

车辆轨迹预测系列 (二)&#xff1a;常见数据集介绍 文章目录 车辆轨迹预测系列 (二)&#xff1a;常见数据集介绍1、NuScenes (2020)&#xff1a;1、下载2、说明 2、Waymo Open Dataset (2020)&#xff1a;1、介绍2、概述3、下载4、教程5、参考 3、Lyft Level 5 (2020)&#xff…

Ubuntu系统如何配置通过图形界面登录root用户

Ubuntu系统中的root账号默认是锁定的&#xff0c;但可以通过设置密码来启用。 需要注意的是&#xff0c;由于root用户具有对系统完全控制的权限&#xff0c;因此在使用root账户时应格外小心。一个错误的命令可能会导致系统损坏&#xff0c;这就是为什么Ubuntu默认不启用root账户…

ELK Kibana搜索框模糊搜索包含不包含

默认是KQL,点击切换Lucene搜索&#xff0c;搜索日志中包含Exception关键字&#xff0c;不包含BizException、IllegalArgumentException、DATA_SYNC_EXCEPTION关键字的日志&#xff0c;如下&#xff1a; message: *Exception AND !(message : *BizException OR message : *Ille…

五十三、openlayers官网示例Layer Spy解析——跟随鼠标透视望远镜效果、图层剪裁

官网demo地址&#xff1a; Layer Spy 这篇实现了鼠标跟随望远镜效果&#xff0c;鼠标移动时绘制一个圆形的剪裁区剪裁上层图层。 container.addEventListener("mousemove", function (event) {mousePosition map.getEventPixel(event);map.render();});container.a…

工具分享:Search_Viewer

文章目录 前言Search_Viewer介绍安装方式使用方式 前言 本文推荐工具Search_Viewer&#xff0c;详细介绍其安装和使用方式。 Search_Viewer介绍 集Fofa、Hunter鹰图、Shodan、360 quake、Zoomeye 钟馗之眼、censys 为一体的空间测绘gui图形界面化工具&#xff0c;支持一键采…

【Python】成功解决TypeError: missing 1 required positional argument

【Python】成功解决TypeError: missing 1 required positional argument 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1…

事件驱动架构详解:触发与响应构建高效系统

目录 前言1. 事件驱动架构概述1.1 什么是事件1.2 事件驱动架构的核心概念 2. 事件驱动架构的实现2.1 基于消息队列的实现2.2 基于发布-订阅模式的实现2.3 基于流处理的实现 3. 事件驱动架构的优势3.1 松耦合性3.2 可扩展性3.3 异步处理3.4 灵活性 4. 事件驱动架构的应用场景4.1…

【论文复现|智能算法改进】改进麻雀算法的无人机三维路径规划

目录 1.UAV路径规划数学模型2.改进点3.结果展示4.参考文献5.代码获取 1.UAV路径规划数学模型 【智能算法应用】蜣螂优化算法DBO求解UAV路径规划 2.改进点 Logistics混沌映射 X n 1 μ X n ( 1 − X n ) , X n ∈ ( 0 , 1 ) (1) X_{_{n1}} \mu X_{_n}( 1 - X_{_n} ) ,\qua…

CSS属性选择器具有不区分大小写的模式

今天&#xff0c;我偶然发现了 caniuse.com 项目的一期&#xff0c;其中提到了新的和即将推出的 CSS Level 4 选择器。 这个列表很长&#xff0c;并且有许多新的选择器正在开发中。一个新的选择器标志引起了我的注意&#xff1b;属性选择器将变成一个 i 标志&#xff0c;这使得…

CRMEB PRO企业微信通讯录配置

企业微信通讯录配置 登录企业微信管理后台 企业微信 1、点击【管理工具】找到【通讯录同步】点击进入 2、点击【开启API接口同步】 进入设置【通讯录同步】页面后&#xff0c;权限一栏&#xff0c;勾选【API编辑通讯录】勾选【开启手动编辑】&#xff1b; 3、点击下图箭头所…

服务端代码编写中MySql大小写在Java中报错问题解决

报错信息&#xff1a; 原因&#xff1a;MySql和Java变量大小写产生的冲突。 经过查阅各个博客等&#xff0c;得出浅显结论&#xff08;不一定对&#xff09;&#xff1a;MySql大小写不敏感&#xff0c;Java大小写敏感&#xff0c;当Javabean转为MySql数据库表时&#xff0c;Ja…

Linux企业 集群批量管理-秘钥认证

集群批量管理-秘钥认证 概述 管理更加轻松&#xff1a;两个节点&#xff0c;通过秘钥认证形成进行访问&#xff0c;不需要输入密码&#xff0c;单向服务要求&#xff08;应用场景&#xff09;&#xff1a; 一些服务在使用前要求我们做秘钥认证 手动写批量管理脚本名字&#x…

Swift Combine — Subject Publishers(PassthroughSubject CurrentValueSubject)

本文主要介绍一下Subject&#xff0c;Subject 本身也是一个 Publisher&#xff0c;其定义如下&#xff1a; public protocol Subject<Output, Failure> : AnyObject, Publisher {func send(_ value: Self.Output)func send(completion: Subscribers.Completion<Self.…

基于jeecgboot-vue3的Flowable流程-自定义业务表单处理(一)支持同一个业务多个关联流程的选择支持

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 这部分先讲讲支持自定义业务表单一个业务服务表单多个流程的支持处理 1、后端mapper部分 如下&#xff0c;修改selectSysCustomFormByServiceName为list对象&#xff0c;以便支持多个 &…

苹果手机短信删除了怎么恢复?有那些方法?

IPhone短信删除怎么恢复&#xff1f;现在大多数人都会使用社交软件沟通交流&#xff0c;短信的用武之地已经没以前那么多&#xff0c;但是它的重要性一点都不能忽视&#xff0c;有些重要的短信内容值得我们保留&#xff0c;如果不小心删除了这些短信内容该怎么恢复&#xff1f;…

全网首测!文生软件平台码上飞CodeFlying,效果炸裂!

前言&#xff1a; 提到AIGC&#xff0c;在大家的印象中应该就是让AI自己生成文字&#xff0c;图片等内容吧。随着今年Sora&#xff0c;Suno的爆火&#xff0c;将AIGC的应用场景又拉到了一个新的高度&#xff0c;为人们带来了更多的遐想。在未来&#xff0c;或许可以用AI来生成…

【LLM-多模态】高效多模态大型语言模型综述

一、结论写在前面 模型规模的庞大及训练和推理成本的高昂&#xff0c;限制了MLLMs在学术界和工业界的广泛应用。因此&#xff0c;研究高效轻量级的MLLMs具有巨大潜力&#xff0c;特别是在边缘计算场景中。 论文深入探讨了高效MLLM文献的领域&#xff0c;提供了一个全面的视角…

简易人工智能入门

一、监督or非监督 监督学习&#xff08;Supervised Learning&#xff09;&#xff1a;训练集有标记信息&#xff08;Y&#xff09;&#xff0c;学习方式有分类和回归 无监督学习&#xff08;Unsupervised Learning&#xff09;&#xff1a;训练集没有标记信息&#xff0c;学习…

20240621将需要自启动的部分放到RK3588平台的Buildroot系统的rcS文件中

20240621将需要自启动的部分放到RK3588平台的Buildroot系统的rcS文件中 2024/6/21 17:15 开发板&#xff1a;飞凌OK3588-C SDK&#xff1a;Rockchip原厂的Buildroot 缘起&#xff1a;在凌OK3588-C的LINUX R4系统启动的时候&#xff0c;需要拉高GPIO4_B5、GPIO3_B7和GPIO3_D0。…