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

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

在这里插入图片描述

入门

什么是依赖注入?

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

依赖注入的类型

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

1. 构造函数注入

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

class AuthService {private let userManager: UserManagerProtocolinit(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: UserManagerProtocolinit(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 inAuthService(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) { _ inUserManager.shared
}// 注册 AuthServiceProtocol 类型
container.register(AuthServiceProtocol.self) { resolver inAuthService(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: UserManagerProtocolinit(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) { _ inUserManager.shared
}// 注册 AuthServiceProtocol 类型
container.register(AuthServiceProtocol.self) { resolver inAuthService(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 CommonModulepublic class AuthService: AuthServiceProtocol {private let userManager: UserManagerProtocolpublic 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  = <<-DESCA longer description of AuthComponent.DESCs.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}'# 依赖 CommonModules.dependency 'CommonModule', '~> 1.0.0'
end

UserComponent

UserManager.swift
import CommonModulepublic 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  = <<-DESCA longer description of UserComponent.DESCs.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 和 AuthComponents.dependency 'CommonModule', '~> 1.0.0's.dependency 'AuthComponent', '~> 1.0.0'
end

Example Project

Podfile
platform :ios, '10.0'target 'ExampleApp' douse_frameworks!# 使用 UserComponent 和 AuthComponentpod '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) { _ inUserManager.shared
}// 注册 AuthServiceProtocol 类型
container.register(AuthServiceProtocol.self) { resolver inAuthService(userManager: resolver.resolve(UserManagerProtocol.self)!)
}// 使用依赖注入
let authService = container.resolve(AuthServiceProtocol.self)!
authService.authenticate()

项目结构

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

说明

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

统计学二学习笔记

假设检验&#xff08;Test of Hypothesis&#xff09; ①Null hypothesis :H0 期望值 ②Althernative hypothesis:Ha 或者H1 拒绝了H0之后要接收的值 ③即使是真的&#xff0c;如果发生的机率很小&#xff0c;我也会拒绝掉你 ④在范围内就接收他的H0值&#xff1a;定义阿尔法…

PhotoShop批量生成存储jpg

1、说明 根据之前自动批量生成psd格式的文件。打印一般都是jpg格式的&#xff0c;那如果将这些psd的文件&#xff0c;生成jpg&#xff0c;本文采用ps的动作 2、生成动作 点击窗口-动作 录屏存储jpg动作 3、根据动作生成 选择相应动作之后选择需要处理的文件夹

洛谷 P10584 [蓝桥杯 2024 国 A] 数学题(整除分块+杜教筛)

题目 思路来源 登录 - Luogu Spilopelia 题解 参考了两篇洛谷题解&#xff0c;第一篇能得出这个式子&#xff0c;第二篇有比较严格的复杂度分析 结合去年蓝桥杯洛谷P9238&#xff0c;基本就能得出这题的正确做法 代码 #include<bits/stdc.h> #include<iostream&g…

NC--介绍-未加密加密后-流量抓包对比

免责声明:本节仅做技术交流与学习... 目录 介绍: 用法: 未加密--流量抓包 加密: 攻击端 靶机 抓包分析: 介绍: nc 是一个Linux环境下常用的工具命令&#xff0c;可以用来帮助开发者查询和解决网路问题&#xff0c;通常被认为是 NetCat 工具的缩写&#xff0c;在网络工具…

Java——泛型

前言&#xff1a; 泛型类&#xff0c;泛型方法&#xff0c;泛型接口&#xff0c;通配符&#xff0c;类型擦除 文章目录 一、 泛型1.1、泛型的基本概念1.2 泛型的使用 三、通配符&#xff08;Wildcard&#xff09;四、类型擦除&#xff08;Type Erasure&#xff09;五、泛型的局…

React Native性能优化红宝书

一、React Native介绍 React Native 是Facebook在React.js Conf2015 推出的开源框架&#xff0c;使用React和应用平台的原生功能来构建 Android 和 iOS 应用。通过 React Native&#xff0c;可以使用 JavaScript 来访问移动平台的 API&#xff0c;使用 React 组件来描述 UI 的…

ONLYOFFICE 文档 8.1 现已发布:功能全面的 PDF 编辑器、幻灯片版式等等

0、前言 在技术的快速发展和工作方式的持续演进下&#xff0c;现代办公软件正变得越来越强大和多样化。ONLYOFFICE&#xff0c;作为市场上备受瞩目的一体化办公解决方案&#xff0c;以其全面的文档编辑、表格处理和演示制作功能&#xff0c;满足了用户在不同办公场景下的需求。…

安卓设备优雅的命令 adb 以及 优秀的控制 scrcpy

一、背景 如果有多台安卓设备&#xff0c;并为这些设备安装软件&#xff0c;一个个使用u盘再加上鼠标操作虽然可以做到&#xff0c;但是大概率比较麻烦。试想下&#xff0c;如果坐在电脑旁边&#xff0c;就能鼠标在电脑上点点就能解决问题&#xff0c;是多么优雅的一件事情。 …

使用上海云盾 CDN 和 CloudFlare 后 Nginx、 WordPress、 Typecho 获取访客真实 IP 方法

最近因为被 DDoS/CC 攻击的厉害,明月就临时的迁移了服务器,原来的服务器就空置下来了,让明月有时间对服务器进行了重置重新部署安装生产环境。因为站点同时使用了上海云盾和 CloudFlare(具体思路可以参考【国内网站使用国外 CloudFlare CDN 的思路分享】一文)两个 CDN 服务…

Docker--基础详解

目录 Docker介绍 Docker与传统虚拟机相比的优势 Docker基础插件 Docker镜像 容器和仓库 Docker介绍 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言开发&#xff0c;遵从Apache2.0开源协议&#xff0c;依赖Linux内核的Cgroup和Namespace等技术&#xff0c;对进…

快速欧氏聚类与普通欧氏聚类比较

1、前言 文献《FEC: Fast Euclidean Clustering for Point Cloud Segmentation》介绍了一种快速欧氏聚类方法,大概原理可以参考如下图,具体原理可以参考参考文献。 2、时间效率比较:快速欧氏聚类VS普通欧氏聚类 网上搜集的快速欧式聚类,与自己手写的普通欧式聚类进行对比,…

“了解MySQL中的enum枚举数据类型“

目录 # 开篇 1. 创建包含枚举类型的表 2. 插入枚举类型的数据 3. 查询包含枚举类型的表 4. 更新枚举类型的数据 5. 使用枚举类型的好处 注意事项 示例总结 附加 # 开篇 在数据库中&#xff0c;枚举&#xff08;ENUM&#xff09;是一种数据类型&#xff0c;用于存储一组…

uniapp中Error: project.configjson: libVersion 字段需为 string. string

错误如下 找到manifestjson文件到源码视图 添加这段代码"libVersion": "latest",即可

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第46课-使用json文件

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第45课-使用头像 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引擎&…

在Maven工程中手动配置并测试SpringBoot(巨详)

本篇博客承继自博客&#xff1a; 在IDEA 2024.1.3 (Community Edition)中创建Maven项目_idea2024.1.3如何创建maven项目-CSDN博客 配置POM文件 打开工程中的pom.xml文件&#xff0c;先向其中写入 <parent><groupId>org.springframework.boot</groupId><…

【挑战100天首通《谷粒商城》】-【第一天】06、环境-使用vagrant快速创建linux虚拟机

文章目录 课程介绍1、安装 linux 虚拟机2、安装 VirtualBoxStage 1&#xff1a;开启CPU虚拟化Stage 2&#xff1a;下载 VirtualBoxStage 2&#xff1a;安装 VirtualBoxStage 4&#xff1a;安装 VagrantStage 4-1&#xff1a;Vagrant 下载Stage 4-2&#xff1a;Vagrant 安装Stag…

[职场] 线上面试的准备工作 #知识分享#经验分享#媒体

线上面试的准备工作 面对求职中的面试&#xff0c;应届毕业生该做些什么准备呢&#xff1f;在这里&#xff0c;向各位分享面试前做好预案不慌张几点准备。现在许多面试是通过线上形式进行的。对于求职者来说&#xff0c;要做好两手准备。在这里&#xff0c;重点与大家分享线上面…

Qt画实时曲线图

Qt引入QcustomPlot 首先下载QcustomPlot源代码&#xff0c;https://github.com/qcustomplot/qcustomplot 下载zip文件 运行所下载的项目生成库文件libqcustomplotd2.a文件和qcustomplotd2.dll文件。 在项目中添加printsupport。 并将qcustomplot.h文件和qcustomplot.cpp文…

基于matlab的图像二值化

1 原理 图像二值化的原理是将彩色或灰度图像转换为只包含两种颜色&#xff08;通常是黑色和白色&#xff09;的二值图像的过程。其关键是通过设定一个阈值&#xff0c;将图像中的像素点的灰度值与阈值进行比较&#xff0c;根据比较结果将像素点设置为白色或黑色。步骤如下&…

ElementPlus组件与图标按需自动引入

按需自动引入组件 1. 安装ElementPlus和自动导入ElementPlus组件的插件 pnpm install element-plus pnpm install -D unplugin-vue-components unplugin-auto-import 2. vite.config.ts进行修改 import { defineConfig } from vite import vue from vitejs/plugin-vue // …