Skip to content

Latest commit

 

History

History
149 lines (133 loc) · 13.3 KB

modificated_injection.md

File metadata and controls

149 lines (133 loc) · 13.3 KB

Модификаторы внедрения

Одна из отличительных возможностей DITranquillity по сравнению со Swinject это модификаторы внедрения. Они позволяют достаточно красиво изменить способ внедрения/получения объекта, прям по месту, а, не указывая его как-то отдельно от самого внедрения/получения. Но зачем много слов? давайте посмотрим на них в деле:

Тэги

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

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

protocol Tag1 {}
enum Tag2 {}
class Tag3 {}
struct Tag4 {}
typealias Tag5 = Tag1 & Tag3

Все описанные здесь варианты могут являться тэгами для библиотеки. Ну а если словами - то любой Уникальный тип, это тэг. При этом typealias на один тип, не порождает новый тип, а создает лишь синоним, этому типу. Я рекомендую для объявления тэгов использовать протоколы - так проще безопасней, и они могут объединяться.

Что такое тэг определились - теперь надо его привязать к объекту/компоненту. Это делается с помощью операции указания сервисов:

container.register(Cat.init)
    .as(Cat.self, tag: Felix.self)
    .as(Animal.self, tag: Felix.self)
container.register(Dog.init)
    .as(Dog.self, tag: Rex.self)
    .as(Animal.self, tag: Rex.self)

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

И после таких вот подготовок переходим к самому важному - как же теперь внедрить тип с использованием тэга. Это сделать достаточно просто:

container.register(Home.init)
    .injection { $0.animal1 = by(tag: Felix.self, on: $1) }
    .injection(\.animal2) { by(tag: Rex.self, on: $0) }

Или даже так:

container.register { 
    Home(animal1: by(tag: Felix.self, on: $0),
         animal2: by(tag: Rex.self, on: $1))
}

Или так:

let animal1: Animal = by(tag: Felix.self, on: container.resolve())
let animal2: Animal = by(tag: Rex.self, on: container.resolve())

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

Во всех трех случаях, в animal1 будет лежать экземпляр класса Cat, а в animal2 будет лежать экземпляр класса Dog.

Множественное внедрение

Более простой, и понятный модификатор внедрения - это множественное внедрение. Он позволяет внедрить массив объектов соответствующих некоему сервису. Пример, когда подобное может понадобиться: Есть протокол который позволяет получить количество новых уведомлений/новостей/сообщений/задач - ну или просто количество чего-то нового. Для получения подобной информации каждый раздел, будет иметь свою собственную реализацию, и лазить по разным методам на сервер, а возможно брать из БД. Соответственно удобно если каждый раздел реализует этот один протокол. Но как собрать в этом случае все эти цифры вместе, чтобы показать на badge общее количество нового?

При использовании различных библиотек, вам бы пришлось или указывать явно типы всех разделов, или добавлять тэги, но не удалось бы получить все объекты соответствующие одному протоколу. В DITranquillity это делает очень легко:

container.register(EventsService.self)
    .as(NewCounter.self)
container.register(NewsService.self)
    .as(NewCounter.self)
container.register(MessagesService.self)
    .as(NewCounter.self)
container.register(TasksService.self)
    .as(NewCounter.self)
...

let anyCounters: [NewCounter] = many(container.resolve())

или, как и в случае с тэгами модификатор можно использовать во всех ситуациях:

container.register { BadgeUpdater(anyCounters: many($0)) }
// или
container.register(BadgeUpdater.init)
.injection(\.anyCounters) { many($0) }

Таким не хитрым способом, можно получать все объекты соответствующие некоему сервису.

Аргумент

Последний модификатор внедрения, это внедрение аргумента. Но прежде чем рассказать о нем, обращаю внимание - это не безопасное API, и если тип аргумента внедряемого, не совпадет с типом аргумента желаемым, то библиотека может упасть во время исполнения, с информацией, какой тип не удалось получить. Но если вы уверены в своих силах, то давайте разбираться, как этим пользоваться. Для того чтобы в компонент можно было внедрять аргументы, как и в прошлых случаях достаточно указать модификатор внедрения:

container.register { Cat(name: arg($0)) }
  .as(Animal.self)
  .injection { $0.owner = arg($1) }
  .injection(\.age) { arg($0) }

При необходимость передать аргумент, только в качестве первого параметра функции инициализации класса, а остальные внедрить обычным образом, есть сокращенный синтаксис:

container.register(Cat.init) { arg($0) }

Сокращение позволяет применить модификатор только к первому параметру функции инициализации, а все остальные оставить как есть - внедрять обычным образом.

Да аргументы можно передавать в любое место, а не только непосредственно в метод инициализации. Дальше обращаю внимание, что их количество может быть любым. В данном примере мы будем передавать три аргумента. При передаче важен порядок - он интуитивен - в каком порядке встречается arg в таком порядке надо будет и передавать аргументы.

Следующая важная особенность. В отличии от наверное всех библиотек для внедрения зависимостей, в DITranquillity аргументы могут передаваться не только на первый получаемый объект, но и на любой объект который создается в ходе создания целевого. Это было сделано для того чтобы при получении ViewController-а можно было передать аргументы сразу в Presenter. Удобно? - Да. Безопасно? - Не очень, так как зависимости являются внутренней информацией объекта. Как же это сделать? Для этого при получении объекта нужно передать специальный объект, с указанием аргументов. В коде это выглядит так:

var arguments = AnyArguments()
arguments.addArgs(for: Cat.self, args: "Felix", "Peter", 2)
let cat: Cat = container.resolve(arguments: arguments)

Такая запись позволяет передать аргументы не в один создаваемый объект, а сразу в несколько, в случае если при получении объекта создаются еще и другие объекты:

var arguments = AnyArguments()
arguments.addArgs(for: Cat.self, args: "Felix", "Peter", 2)
arguments.addArgs(for: Dog.self, args: "Rex", "Peter", 4)
let home: Home = container.resolve(arguments: arguments)
// home.cat.name == "Fefix"
// home.dog.name == "Rex"

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

let arguments = AnyArguments(for: Cat.self, args: "Felix", "Peter", 2)
let cat: Cat = container.resolve(arguments: arguments)

И есть еще более короткая и привычная запись, в случае если аргументы нужно передать только в получаемый тип:

let cat: Cat = container.resolve(args: "Felix", "Peter", 2)

При использовании этих возможностей стоит учитывать важные моменты:

  • Порядок аргументов важен - внутри нет проверки на совпадение типов, и если тип не совпадет, то библиотека не создаст объект
  • Если во время получения зависимости, нужно будет создать несколько объектов, то каждому объекту передастся список аргументов по отдельности.
  • Библиотеке не обязательно указывать тип имплементации. Если у вашего типа при регистрации были указаны сервисы то указать аргументы можно и для типов сервисов:
let arguments = AnyArguments(for: Animal.self, args: "Felix", "Peter", 2)
let cat: Cat = container.resolve(arguments: arguments)

Существует более безопасный, и более красивый способ передачи аргументов - воспользоваться отложенным внедрением. Подробно про него написано на странице, тут будет лишь небольшой пример:

class Home {
    let catMaker: Provider3<Cat, String, String, Int>
    init(catMaker: Provider3<Cat, String, String, Int>) {
        self.catMaker = catMaker
    }
    ...
    func makeCat(name: String, owner: String, age: Int) -> Cat {
        return catMaker.value(name, owner, age)
    }
}

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