Search
Write a publication
Pull to refresh
3
0

Пользователь

Send message

Спасибо за статью. Она натолкнула на идею, что если бы у контейнера была ссылка на родительский контейнер (конкретного типа, а не протокола 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 – вьюконтроллеры (да и все классы, которым нужно предоставить зависимости) не зависят от контейнеров – вместо них используются обычные функции, которые возвращают объект нужного типа.

Note: Заметьте, что используется не register, а register1. К сожалению, так необходимо указывать, если объект имеет в инициализаторе одну и только одну зависимость.

Эту проблему можно решить, используя в получаемой функции кортеж из n-элементов вместо n-аргументов.


func register<D, R>(_ factory: @escaping (D) throws -> R) {
    print("register 1")
}

func register<D1, D2, R>(_ factory: @escaping ((D1, D2)) throws -> R) {
    print("register 2")
}

func myFunc(arg1: String, arg2: String) -> String {
    return arg1 + arg2
}

register(myFunc)

register { (arg1: Int, arg2: Int) -> Int in
    return arg1 + arg2
}

Компилятор не будет ругаться на неоднозначность и будет выбирать корректную перегрузку.


Примеры использования этого приёма в других DI-фреймворках: 1, 2, 3

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Date of birth
Registered
Activity