Skip to content

Latest commit

 

History

History
69 lines (57 loc) · 5.4 KB

container_hierarchy.md

File metadata and controls

69 lines (57 loc) · 5.4 KB

Иерархия контейнеров

Эта возможность конфликтует с общей концепцией библиотеки, но ей нельзя пренебрегать в случае наличия старого кода, который не был написан с использование DI контейнеров.

Если мы говорим про иерархию контейнеров, то это означает, что у нас должен быть не один контейнер, что приводит к одной заметке:

Важно - если у вас в программе есть создание DI контейнеров во время исполнения кода, а не в самом начале его исполнения, то валидация графа не может гарантировать достоверности результатов. Поэтому не рекомендую создавать много контейнеров особенно во время исполнения кода.

Если же по той или иной причине вы решили в своём коде использовать вариант, когда у вас больше одного контейнера, то давайте разбираться, что в этом случае как работает.

Предположим у вас есть три контейнера:

let container1 = DIContainer()
let container2 = DIContainer()
let container3 = DIContainer()

И в каждом из них хранятся собственные компоненты:

container1.register(A.init)
    .lifetime(.perContainer(.strong))

container2.register(B.init)
    .lifetime(.prototype)

container3.register(A.init)
    .lifetime(.perContainer(.strong))
container3.register(C.init)
    .lifetime(.prototype)

Из каждого контейнера можно получить собственные объекты, и они никак не будут пересекаться друг с другом. За исключением если эти объекты не зарегистрированы как perRun или single - в этом случае объект будет единственный на компонент, но не обязательно единственный на тип. В чем разница?

func singleComponent(container: DIContainer) {
    container.register(OtherType.init)
        .lifetime(.perRun(.strong))
}

container1.register(Type.init)
    .lifetime(.perRun(.strong))
container2.register(Type.init)
    .lifetime(.perRun(.strong))
    
singleComponent(container: container1)
singleComponent(container: container2)

В данном примере тип Type имеет два компонента, и даже несмотря на то, что тип один, и он имеет время жизни perRun это всё равно в памяти будет два разных объекта. А вот OtherType будет одним и тем же компонентом в двух разных контейнерах, и в силу его времени жизни экземпляр OtherType будет один и тот же для двух контейнеров.

Возвращаемся к иерархии - при создании контейнера можно указать родительский:

let container1 = DIContainer()
let container2 = DIContainer(parent: container1)
let container3 = DIContainer(parent: container2)

Данный синтаксис означает, что если в контейнере из которого происходит resolve нет желаемого зарегистрированного типа, то поиск пойдет в родительский контейнер - вдруг в нем есть. Используя код выше получаем:

let a1: A? = container1.resolve() // a1 != nil
let a2: A? = container2.resolve() // a2 != nil && a2 === a1
let a3: A? = container3.resolve() // a3 != nil && a3 !== a2 && a3 !== a1

let b1: B? = container1.resolve() // b1 == nil
let b2: B? = container2.resolve() // b2 != nil
let b3: B? = container3.resolve() // b3 != nil && b3 !== b2

let c1: C? = container1.resolve() // c1 == nil
let c2: C? = container2.resolve() // c2 == nil
let c3: C? = container3.resolve() // c3 != nil

Думаю, почему так происходит с объектами типа C понятно.

При создании b3 он создается из второго контейнера, но его время жизни prototype и поэтому b3 не тот же объект что и b2.

С объектами типа A все сложнее. Вначале создается a1 и ложется в кэш первого контейнера. Потом создается a2 который берется из кэша первого контейнер, так как во втором контейнере нет регистрации такого типа. В конце, создается a3 который будет создан на основе регистрации в третьем контейнере, а не пойдет вниз по иерархии. Из-за этого a3 будет другим объектом.