抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

依赖注入

相信依赖注入这一能力不需要过多解释,但是Swift下的设计还是可以大致分析。

整体思路

从业务中我们得出了一些设计的理念,并将其表述如下:

  • 应当有一个无感知的中心存储,我将其称为 上下文

    • 上下文应当是可以随意拓展的、存在层级结构和同级互相隔离的能力
    • 存储的引用必须是弱引用,从而不影响注册的依赖的正常生命周期
    • 可以快查上下文
  • 应当提供足够丰富的DI能力

    • 注册
      • 支持单一上下文
      • 支持多上下文
    • 注入
      • 支持懒加载的注入
      • 支持不赋值不持有的快取方式注入
      • 支持高层上下文下注入该上下文所属子上下文的依赖
  • 注册与注入的是 实现Protocol的实例,而不是某个Class的实例

上下文

上下文 Context 是整个 DI 的核心,用于存储依赖的弱引用

首先介绍下它的基础设施

上下文采用 Weak-value Dictionary 来存储依赖,以防止出现ARC下被强引用导致影响生命周期乃至内存泄漏

WeakValueDictionary实际上是利用泛型的一个好例子,参考了Dictionary的做法,我们通过泛型来确定具体类型

1
2
3
4
5
class WeakValueDictionary<Key:Hashable,Value>{
private var storage = [Key:WeakValueReference<Value>]()
init(){
}
}

Weak value 的实现通过自定义的一个struct的计算属性来做到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct WeakValueReference<Value>{
private weak var reference : AnyObject?

init (value:Value){
reference = value as AnyObject
}

var value : Value?{
get {
if let ref = reference as? Value{
return ref
}else{
return nil
}
}
}
}

而Key值采用从Protocol.Type 转化而来的String值来满足Hashable

1
String(reflecting: aProtocol)

而为了在undefined状态/高层Context中注入低层Context中已注册的依赖,则使用Queue来完成层序遍历

再聊一下整个的结构

Context是一个可自由定制无限拓展的树状结构,主要功能是依赖的存储

1
2
3
4
5
class UDIContext{
public var tag : String
private var dependencies = WeakValueDictionary<String,UDIObject>()
internal var subContexts = [UDIContext]()
}

而AppContext 是其顶部节点,也是 undefined 的情况下所使用的默认节点

1
let AppContext = UDIContext(withTag: "AppContext")

树状结构即可满足不同上下文之间的隔离,这样,在不同的上下文可以注入同一协议的不同实例来实现更精准的控制

Context

总体的结构就如同下面的图片一样

其中Manager没有什么能力,只是对Context的一层封装与统一管理

structure

DI对象协议

Swift没有隐形基类,因此需要让所有需要注入、需要注册的对象都遵循DI对象协议 UDIObject

只有遵循该协议才能调用各项能力进行以来的注册和注入以及其他操作

1
2
3
4
5
public protocol UDIObject : AnyObject {
func didAttachContext()

var _attachedContext : UDIContext? {get set}
}

协议声明其必须是AnyObject,既是为了照顾上文Weak value Dictionary 的实现,也是出于实用性考虑,实例毕竟是最需要注入的依赖,当然后续可以进行更多拓展

这个协议非常简单,didAttachContext()是交由各实例自行实现的,其调用是由下文中所提到的计算属性来完成;而_attachedContext是一个遗留的小问题,主要是为计算属性服务,不应直接使用

而协议真正的实现都是依赖extension来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extension UDIObject{
public var attachedContext : UDIContext? {
set{
self._attachedContext = newValue
self.didAttachContext()
}
get{
return _attachedContext
}
}

public func UDILink<aProtocol>(property:inout aProtocol?){
//...
}

public func UDILinkInLine<aProtocol>(_ aProtocolType:aProtocol.Type) -> aProtocol?{
//...
}

public func UDIBind<Property:UDIObject>(property: Property,aProtocol:Any){
//...
}

public func UDIMultiBind<Property:UDIObject>(property: Property,aProtocol:Any){
//...
}

public func UDIFind(_ tag:String) -> UDIContext? {
//...
}

}

在这里我们依靠这几个暴露的API引入一些概念

Link/LinkInLine

依赖注入,区别在于Link所做的是普通的赋值操作,而LinkInLine更偏向于实时注入与使用,且不会产生持有的导致的各种问题

Bind/MultiBind

依赖注册,区别在于Bind为单一Context注册,而MultiBind可以在多个Context注册,视具体场景来进行区别使用

在这些API中,我们广泛使用了泛型,下面就进行一些API的具体分析,来分析泛型、Swift类型中的一些trick

泛型

在这里我们主要讨论一下泛型中类型推断的一些trick (如果想学习泛型,可以直接去看swift doc或者翻译好的cnswift),主要从Link和LinkInLine两个API的实现入手

Link

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public func UDILink<aProtocol>(property:inout aProtocol?){
usageCheck(aProtocol.self)
if (self.attachedContext != nil){
guard let _property : aProtocol = UDIManager.linkObj(in: self.attachedContext!) else {
property = nil
return
}
property = _property
}else{
guard let _property : aProtocol = UDIManager.linkObj(in: AppContext ) else {
property = nil
return
}
property = _property
}
}

在上面这个例子里,Link是如何自动判断属性的类型的?

实际上,我们是这样声明property的,我们的整个DI的理念是面向协议的,我们只需要注入实现该协议的实例,而不关注其类型

1
var myObj : myProtocol?

而Swift可以在这样的声明下,编译器正常判断其类型为 Optional<myProtocol>

LinkInLine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public func UDILinkInLine<aProtocol>(_:aProtocol.Type) -> aProtocol?{
usageCheck(aProtocol.self)
if (self.attachedContext != nil){
guard let _property : aProtocol = UDIManager.linkObj(in: self.attachedContext!) else {
return nil
}
return _property
}else{
guard let _property : aProtocol = UDIManager.linkObj(in: AppContext) else {
return nil
}
return _property
}
}

这是一个Swift泛型的很好展现

我们知道我们传递参数时,无论是Protocol.Type还是Protocol.self实际上都是在传递其类型属性

因此当我们的参数声明其类型为aProtocol.Type,编译器就可以正常依据参数输入推断出aProtocol这一泛型的真实类型

附录

评论