Комментарии 6
Не совсем понятно что берет на себя генератор кода и как выглядит код до и после использования Needle.
Конечная цель генератора кода заключается в предоставлении DI-контейнеру зависимостей из других DI-контейнеров.
Допустим, что у нас есть такой контейнер:
protocol SomeUIDependency: Dependency {
    var router: Router { get }
}
final class SomeUIComponent: Component<SomeDependency> {
    var someObject: SomeObjectClass {
        shared {
            SomeObjectClass(router: dependecy.router)
        }
    }
}

router — это зависимость, которая предоставляется каким-то другим DI-контейнером. Пусть это будет RouterComponent.
Тогда генератор кода для SomeUIComponent создаст DependencyProvider, который может выглядеть следующим образом:
private class SomeUIComponent34170097de9901d91dcProvider: SomeUIDependency {
    var router: Router {
        return routerComponent.router
    }
    private let routerComponent: RouterComponent
    init(routerComponent: routerComponent) {
        self.rootComponent = routerComponent
    }
} 

Для связи между различными DependencyProvider и контейнерами генератор кода создает глобальную функцию registerProviderFactories(),
которую мы должны вызвать в своем коде до начала использования DI-контейнеров.
Внутри этой функции для рассмотренного SomeUIComponent будут находится следующие строки:
__DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: "^->RootComponent->SomeUIComponent") { component in
        return SomeUIComponent34170097de9901d91dcProvider(component: component)
}

Если какая-то зависимость не находится, то генератор кода достаточно информативно оповещает об этом:
warning: Could not find a provider for (router: Router) which was required by SomeUIDependency, along the DI branch of ^->RootComponent->SomeUIComponent.
warning: Missing one or more dependencies at scope.
error: Some dependencies are missing, please look at the warnings above for the list.
И что с DependencyProvider потом делать? К тому что вот я использую RIBs. Там это дерево уже генерится шаблоном модуля. Чем Needle упрощает жизнь?
C DependencyProvider ничего делать не нужно. Он будет зарегистрирован для конкретного контейнера во внутреннем регистре фабрик __DependencyProviderRegistry в сгенерированном коде.
У каждого контейнера, который наследуется от базового класса Component, есть следующий конструктор:
public init(parent: Scope) {
    self.parent = parent
    dependency = createDependencyProvider()
}

В методе createDependencyProvider() и происходит обращение к фабрике, которая создает DependecyProvider без каких-либо дополнительных действий со стороны разработчика:
private func createDependencyProvider() -> DependencyType {
    let provider = __DependencyProviderRegistry.instance.dependencyProvider(for: self)
    if let dependency = provider as? DependencyType {
        return dependency
    } else {
        // This case should never occur with properly generated Needle code.
        // Needle's official generator should guarantee the correctness.
        fatalError("Dependency provider factory for \(self) returned incorrect type. Should be of type \(String(describing: DependencyType.self)). Actual type is \(String(describing: dependency))")
    }
}

В RIBs у модуля может быть сущность Component, которая отвечает за зависимости модуля и помогает Builder-у в создании частей модуля. Вот этот компонент как раз может быть DI-контейнером Needle.
Не все проекты используют RIBs. Needle позволяет получить такое дерево контейнеров для любого архитектурного подхода (MVVM, MVC, etc).

Спасибо за статью. Она натолкнула на идею, что если бы у контейнера была ссылка на родительский контейнер (конкретного типа, а не протокола Scope как у Needle), то можно организовать поиск зависимости по иерархии контейнеров средствами языка. Вот пример кода:


protocol ContainerType {
    associatedtype Parent: ContainerType
    var parent: Parent { get }
}

@dynamicMemberLookup
struct ContainerExtension<Container> where Container: ContainerType {
    let container: Container

    subscript<T>(dynamicMember keyPath: KeyPath<Container, T>) -> T {
        container[keyPath: keyPath]
    }

    subscript<T>(dynamicMember keyPath: KeyPath<ContainerExtension<Container.Parent>, T>) -> T {
        ContainerExtension<Container.Parent>(container: container.parent)[keyPath: keyPath]
    }
}

ContainerExtension – вспомогательный тип для поиска зависимости. Его первый subscript предоставляет доступ по ключам (KeyPath) текущего контейнера, второй – по ключам ContainerExtension родительского. Т.о. поиск идёт сначала в текущем контейнере, затем в его родителе, затем родителе родителя и т.д. Автодополнение в XCode показывает свойства всех контейнеров в иерархии. И это здорово. Ещё одна особенность решения с dynamicMemberLookup – если у нескольких контейнеров есть свойства с одним именем, но разных типов – будет выбрано свойство желаемого типа (тип можно указать as MyService).


Для каждого такого протокола генератор анализирует доступность каждой зависимости путем поиска ее среди родителей контейнера. Поиск идет снизу вверх, т.е. от дочернего компонента к родительскому.

Зависимость считается найденой только если совпадают имя и тип зависимости.

Выходит, что данный алгоритм можно выразить с помощью Swift и который выполняется на этапе компиляции.


Я написал прототип DI фреймворка, который использует такой подход – AtomicDI. Он имеет малый размер, как и Needle использует ручное разрешение зависимостей, но не использует кодогенерацию и регистр.
Для примера проекта, использующего этот di, я взял пример из needle и переделал на использование AtomicDI (основной код в файле DIConfiguration.swift). Подход отличается от needle – вьюконтроллеры (да и все классы, которым нужно предоставить зависимости) не зависят от контейнеров – вместо них используются обычные функции, которые возвращают объект нужного типа.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.
Информация
Дата основания

1 июля 2016

Местоположение

Латвия

Численность

201–500 человек

Дата регистрации

7 ноября 2018

Блог на Хабре