Swinject 源码框架(二):循环依赖的解决

可能存在循环依赖,比如 Parent 强制有 ChildChild 弱持有 Parent
具体实现如下。Parent 初始化时,必须传入 Child,而 Child 初始化不必传入 Parent

protocol ParentProtocol: AnyObject { }
protocol ChildProtocol: AnyObject { }

class Parent: ParentProtocol {
    let child: ChildProtocol?

    init(child: ChildProtocol?) {
        self.child = child
    }
}

class Child: ChildProtocol {
    weak var parent: ParentProtocol?
}

具体调用方式如下:

let container = Container()
container.register(ParentProtocol.self) { r in
    let child = r.resolve(ChildProtocol.self)!
    let parent = Parent(child: child)
    return parent
}
container.register(ChildProtocol.self) { _ in Child() }
    .initCompleted { r, c in
        let child = c as! Child
        child.parent = r.resolve(ParentProtocol.self)
}

let parent =  container.resolve(ParentProtocol.self)

注意把 Child 的属性赋值放在了 initCompleted 里。

结合代码看时序

循环依赖

// 如果已经存在实例了,就直接返回。可以避免了无限循环
if let persistedInstance = entry.storage.instance(inGraph: currentObjectGraph), let persistedService = persistedInstance as? Service {
    return persistedService
}

//生成一个新的实例
let resolvedInstance = invoker(entry.factory as! Factory)

// 最开始没有实例,可能在调用invoker后,可能已经有新的实例在entry 的 strage 里了,比如上面的 P1。
if let persistedInstance = entry.storage.instance(inGraph: currentObjectGraph), let persistedService = persistedInstance as? Service {
    // An instance for the key might be added by the factory invocation.
    return persistedService
}
//把生成的实例保存起来
entry.storage.setInstance(resolvedInstance as Any, inGraph: currentObjectGraph)

// 初始化完成以后,调用 initComplete 
if  let completed = entry.initCompleted as? (Resolver, Any) -> Void,
    let resolvedInstance = resolvedInstance as? Service {

    completed(self, resolvedInstance)
}

return resolvedInstance as? Service

依赖的类型

上面的例子中,ChildParent 的初始化参数,ParentChild 的属性。这种依赖称为 Initializer/Property Dependencies.
也可能 ChildParent 分别是其依赖的属性,这种依赖称为 Initializer/Property Dependencies。
也可能 ChildParent 分别是其依赖的初始化参数。这种依赖,使用 Swinject,暂时无法解决无限循环的问题。

循环依赖可能导致的问题

某一个factory方法可能会被执行两次。比如上面例子中,Parent 对应的 factory 方法被执行了两次。
可能导致一些副作用,比如更加耗时。
一种解决方案是把依赖都放进在initCompleted 闭包里,这也意味着不能使用 Initializer/Property Dependencies

container.register(ParentProtocol.self) { _ in Parent()
    }.initCompleted { (r, p) in
        let parent = p as! Parent
        parent.child = r.resolve(ChildProtocol.self)!
}
container.register(ChildProtocol.self) { _ in Child()}
    .initCompleted { r, c in
        let child = c as! Child
        child.parent = r.resolve(ParentProtocol.self)
}

源码中和循环依赖有关的变量

  • resolutionDepth
    每次调用resolve自增。如果有循环依赖,会调用多次。
  • maxResolutionDepth
    用来探测无限循环
  • currentObjectGraph
    标记某次生成实例的过程
  • GraphStorage
    在某次解决循环依赖过程中,生成的实例都存储在这里。

posted on 2019-01-19 21:03  花老🐯  阅读(674)  评论(0编辑  收藏  举报

导航