Всем привет, меня зовут Виталий, я iOS-разработчик в юните мессенджера в Тинькофф. Сегодня поговорим о том, что такое DI, зачем он нужен, рассмотрим известные библиотеки для DI и сравним их между собой. Поехали! Dependency Injection, или внедрение зависимостей, — это паттерн настройки объекта, при котором зависимости объекта задаются извне, а не создаются самим объектом. Другими словами, объекты настраиваются внешними объектами.  Существует несколько способов внедрения зависимостей. Interface injection (через интерфейс)   2. Property injection (через публичное свойство)   3. Method injection (через публичный метод)   4. Constructor injection (через инициализатор/конструктор)   Предпочтительным, как правило, является Constructor injection, так как он лишен недостатков трех предыдущих способов: Interface Injection требует создания дополнительного протокола для инъекции в каждый объект, что кажется избыточным. Property Injection позволяет создать объект и начать его использовать еще до того, как заинжектили все нужные зависимости. Это может приводить к ошибкам из-за неконсистентного состояния объекта. Кроме того, нам приходится делать внедряемые поля публичными, что дает возможность менять их извне в любой момент, нарушая инкапсуляцию.  Method injection очень похож на Property Injection, но внедрение происходит через метод. Используется реже, чем Property Injection, но имеет те же недостатки. Но даже Constructor injection можно использовать не всегда — например, когда есть цикл зависимостей (пример будет чуть ниже). Еще бывает так, что использовать Constructor injection не очень удобно. Например, когда зависимости создаются уже после создания основного объекта. Конечно, можно использовать Constructor injection, если сделать зависимость опциональной и передавать в конструктор как nil по умолчанию. Но в таких кейсах обычно применяется Property injection. Некоторые разработчики считают, что Constructor injection плох тем, что приводит к появлению boilerplate кода в разрастающемся от количества зависимостей инициализаторе, но, думаю, это не плохо. Такие иниты выступают отличным индикатором того, что вы помещаете в объект слишком много зависимостей и, возможно, стоит вспомнить про Single Responsibility Principle и подумать о лучшей декомпозиции объекта. В чем преимущества от использования DI? DI делает зависимости объекта явными (четко прописанными в интерфейсе объекта) — это позволяет лучше контролировать его сложность. DI делает зависимости объекта внешними (передаются в объект извне, а не создаются внутри) — это позволяет отделять код создания объектов от бизнес-логики, улучшая разделение ответственности. DI дает возможность сделать зависимости гибкими. Mожно подменить объект другим — например, если закрыть его протоколом, скрыв реализацию. Код классов теперь зависит только от интерфейсов, а не от конкретных классов, скрывая детали реализации и уменьшая связанность кода. Как следствие — объекты становятся легко тестируемыми.  DI уменьшает связанность(coupling) объектов. DI упрощает переиспользование объектов. DI улучшает декомпозицию за счет выноса порождающей логики наружу. Какие минусы у этого подхода? Увеличивается количество сущностей в проекте. Добавляются дополнительные классы с порождающей логикой и протоколы, скрывающие детали реализации зависимостей. Возрастает время написания кода. Когда говорят про DI в iOS-проектах, часто приводят в пример Service Locator (SL) — паттерн, суть которого в наличии объекта-реестра, к которому обращаются объекты для получения зависимостей. Объект-реестр знает, как получить все зависимости, которые могут потребоваться.  В статьях и iOS-коммьюнити в целом есть некая неоднозначность трактовки понятий ServiceLocator, DI, DI-контейнер, IoC и IoC-контейнер. Не буду на этом подробно останавливаться, просто оставлю ссылку, где, как мне кажется, хорошо разобран этот вопрос. Отмечу только, что ключевым отличием паттернов SL и DI является то, что к локатору обращаются явно внутри класса и достают из него необходимые зависимости. При DI же, наоборот, зависимости внедряются извне. Как видим, реализация довольно проста. Внутри находится словарь, где ключом является строка, содержащая имя типа, а значением — объект, который мы регистрируем в локаторе. Чтобы получить какую-то зависимость, нужно ее сначала зарегистрировать. Зависимости, как правило, регистрируются в одном месте на старте приложения. Данная реализация не является потокобезопасной, но для демонстрации сути паттерна этого вполне достаточно. Кроме того, мы реализовали SL как singleton, что, строго говоря, не обязательно. Давайте рассмотрим пример использования SL. Допустим, раньше вы создавали зависимости прямо по месту использования: С SL вы будете регистрировать зависимости где-то в одной точке при старте приложения и позже получать их через локатор: В чем профит? Давайте разберемся. Плюсы: Можно получить любую необходимую зависимость, скрывая от пользователя детали создания объекта-зависимости. Избавляет от необходимости использовать сервисы-синглтоны. Удобно тестировать — можно подменить зависимости при регистрации на моки. Минусы: Часто является синглтоном. Синглтоны сами по себе имеют много недостатков и часто позиционируются как антипаттерны. Проблемы синглтонов:    - глобальное состояние и его неявное изменение;   - неявная зависимость от синглтона, что приводит к неявному нарушению SRP;   - жесткая зависимость от синглтона, что мешает тестированию. Является god-объектом, который знает слишком много и имеет доступ к любому объекту. А значит, все, кто имеет к нему доступ, получают те же возможности. В сочетании со свойством синглтона проблема усугубляется. Способствует созданию внутренних неявных зависимостей, что приводит к неявной связанности (coupling), которая приводит к неявной сложности.  Ошибки в рантайме: если вы резолвите зависимость, которую не регистрировали, то узнаете об ошибке только в рантайме. Как было сказано выше, SL не обязательно должен быть синглтоном. Мы можем, например, инжектить его в объекты через Constructor Injection: Но тогда зависимости нашего класса все еще остаются неявными. В этом примере можно заметить еще один недостаток SL — необходимость использовать implicitly unwrapped optional либо при каждом резолве проверять опционал на nil. Дело в том, что мы никогда не знаем, регистрировали ли мы нужный нам сервис в локаторе или нет, пока не попробуем зарезолвить его. Мы не получим никаких ворнингов или ошибок при сборке, если какой-то сервис не был зарегистрирован, — просто поймаем краш в рантайме. Чтобы улучшить последний пример, можно вынести SL в фабрику: Так мы реализуем принцип наименьших привилегий и избавляем объекты от необходимости знать про SL (то есть знать слишком много), оставляя его только в области видимости фабрик. Но это не избавляет нас от необходимости передавать объект SL между фабриками. Кто-то должен будет хранить этот объект, ведь он будет нужен в каждой фабрике каждого модуля приложения. Неплохим вариантом такого хранителя является координатор. Использование DI(CI) + Factory + SL + Coordinator позволяет сгладить описанные выше недостатки сервис-локатора. Но тогда возникает резонный вопрос: а нужен ли здесь тогда вообще сервис-локатор? Почему бы не отказаться от него совсем и создавать зависимости внутри фабрик — в конце концов, фабрики и задуманы для инкапсуляции порождающей логики. Оставляю этот вопрос вам на подумать. Итак, время познакомиться с популярными библиотеками для DI. Начнем с классификации. DI-библиотеки, которые мы рассмотрим, будут основаны либо на рефлексии, либо на кодогенерации. Начнем с библиотек, основанных на рефлексии. Под рефлексией подразумевается отображение множества ключей на множество объектов. Например, в нашем сервис-локаторе мы использовали словарь (Dictionary), который, по сути, отображает ключ (имя типа объекта) на сам сервис-объект.  https://github.com/Swinject/Swinject Более 4.5k звезд на «Гитхабе». Наверное, самая популярная DI-библиотека для iOS. Довольно проста в использовании: Но погодите! register...resolve… вам это ничего не напоминает? Это же очень похоже на уже хорошо знакомый нам ServiceLocator. Если мы заглянем внутрь, то увидим аналогичный словарь, но ключом уже является структура ServiceKey, а значением — некий объект за протоколом ServiceEntryProtocol. В нашем примере простого SL мы использовали в качестве ключа строку с именем типа. Здесь все немного сложнее. Тоже используется тип сервиса (serviceType), но также учитываются типы аргументов, передаваемые при резолве. Имя (name) нужно, чтобы добавлять зависимости одного типа под разными именами, опции (option) — для использования контейнера со сторибордами. По сути, все эти параметры нужны, чтобы получать уникальное значение хэша для разных зависимостей, которые мы регистрируем. ServiceEntryProtocol - это уже не объект-сервис, как в нашем простеньком сервис-локаторе. За этим протоколом стоит класс ServiceEntry, который знает, как создавать объект, где и как его хранить (зависит от скоупа), а также содержит необходимые данные для разруливания циклических зависимостей (через initCompleted). Что такое циклические зависимости? Чтобы разобраться, сначала нужно понять, как контейнер создает зависимости. Когда вы запрашиваете у контейнера зависимость некоторого типа, она тоже может иметь зависимости, которые тоже могут иметь зависимости и так далее. При создании зависимости контейнер строит ориентированный граф зависимостей, основываясь не только на связях между ними, но и на типе их скоупа. Скоуп (scope) — это тип, описывающий время жизни регистрируемой зависимости.  Swinject предоставляет четыре типа скоупа (но можно добавить и свой собственный скоуп): Graph, Transient, Container, Weak. Дефолтный скоуп в Swinject. Зависимость разруливается в рамках построенного графа зависимостей. Например: Для каждого резолва a1 и a2 строятся отдельные графы: Важно понимать, что если вы запустите этот код, то поймаете ошибку в рантайме, потому что получите бесконечный цикл создания зависимостей. Такие кейсы как раз разруливаются с помощью .initCompleted, но об этом чуть позже. Он же prototype или unique в других библиотеках. Зависимость создается заново при каждом резолве. Зарегистрируем A со скоупом transient: Получим следующий граф при резолве: Он же singleton в других библиотеках. Думаю, суть понятна: один экземпляр на контейнер и дочерние контейнеры. Создается при первом резолве. Зарегистрируем B со скоупом container: Получим следующий граф при резолве: Он же WeakSingleton. Зависимость будет существовать, пока на нее есть сильная ссылка снаружи. С графом, думаю, понятно — аналогично container. Разруливание циклических зависимостей — мастхэв-фича для DI-библиотеки. В Swinject она реализуется через отложенную property injection внутри метода initCompleted для одной или нескольких зависимости в цикле.  То есть не получится использовать только constructor injection, чтобы разрулить цикл. Например, для цикла, который мы рассматривали выше, можно переделать класс С, чтобы он поддерживал property injection, и добавить его реализацию в initCompleted: При резолве циклических зависимостей в Swinject последние могут создаваться несколько раз, что аффектит время резолва, и на больших циклах могут прилично аффектить время запуска приложения. Кроме того, нельзя точно сказать, какой именно из создавшихся объектов будет использован. В таких случаях лучше вынести все резолвы в цикле в initCompleted, что поможет избежать этой проблемы (подробнее — здесь). Swinject также предоставляет упрощенный синтаксис для регистрации — Autoregistration. То есть вместо того, чтобы писать так: Можно писать намного короче: Удобная фича, но за удобство приходится платить скоростью. Использование авторегистрации замедляет время резолва, а значит, и время запуска приложения. На больших проектах при очень большом количестве зависимостей это будет критично. Насколько именно критично — узнаем чуть позже, а пока предложу альтернативу, которая чуть короче обычного синтаксиса, но быстрее авторегистрации: И это все! Теперь можно писать чуть короче: Плюс ко всем вышеописанным плюшкам Swinject также дает возможность: Строить иерархии контейнеров. Разбивать регистрацию на модули (Assembly). Работать со сторибордами. Обеспечить потокобезопасность. Container не потокобезопасен по умолчанию, но метод synchronize дает нам потокобезопасный резолвер: Главный недостаток Swinject в том, что его очень легко можно использовать как сервис-локатор. В таком случае он наследует все проблемы, описанные в разделе о SL. Swinject тоже имеет runtime-природу со всеми вытекающими. Если вы резолвите зависимость, которую не регистрировали, то узнаете об этом только при краше в рантайме. Вы можете сказать, что с вами этого не случится, так как вы никогда не забываете регистрировать зависимости. Но реально в больших проектах такое вполне может происходить. Например, может потребоваться добавить аргумент при резолве уже существующей зависимости, которая используется в разных модулях. С этого момента при резолве без аргумента вы получите краш в рантайме. Забыли добавить аргумент при резолве в одном из модулей? Что вам об этом скажет Swinject? К сожалению, ничего. https://github.com/AliSoftware/Dip 888 звезд на «Гитхабе». Тоже довольно популярная библиотека, очень похожая на Swinject в плане синтаксиса: Обладает практически таким же набором фич, как Swinject, с несколькими отличиями: Нет поддержки иерархии контейнеров и разбиения регистрации на модули. Потокобезопасность — по умолчанию (можно отключить через аргумент в DependencyContainer.init). Есть фича валидации. Можно провалидировать контейнер, что все зависимости, которые были зарегистрированы, будут зарезолвлены без ошибок.  Кроме уже описанных скоупов, которые есть у Swinject, добавляет также EagerSingleton — «нетерпеливый синглтон». Суть в том, что этот синглтон создается при вызове container.bootstrap(), а не при первом резолве синглтона. Метод bootstrap фиксирует зависимости в контейнере, не давая добавить новые зависимости или удалить старые после его вызова. Внутри устроен похожим на Swinject образом: Ключ также хранит информацию о типе и аргументах. Тэг — аналог поля name в Swinject — используется для регистрации одного типа под разными тэгами. Definition — обертка для зависимости, которая знает ее тип, скоуп и как создавать зависимость.  Как и Swinject, DIP может быть легко использован как сервис-локатор со всеми вытекающими. Некоторые разработчики предпочитают использовать DIP на старте проекта или на небольших проектах, объясняя это тем, что он проще, чем Swinject (спорное утверждение). Но когда «небольшой проект» разрастается, они с ужасом замечают, что время запуска приложения стало чертовски долгим. Дело в том, что DIP значительно медленнее, чем Swinject. Хотите узнать насколько? Не переключайтесь. https://github.com/ivlevAstef/DITranquillity 316 звезд на «Гитхабе». Синтаксис немного отличается от Swinject и DIP. Во-первых, «авторегистрация» из коробки, что удобно, но за это приходится платить увеличением времени резолва. Во-вторых, resolve без необходимости использовать ! и try! — тоже хорошо. Потокобезопасность также из коробки, отключить нельзя. Доступна модульность и иерархии контейнеров.  Несколько новых скоупов: perRun (один на каждый запуск приложения), perContainer (один на каждый контейнер) и single (типа синглтон, но создается сразу после вызова .initializeSingletonObjects() у контейнера).  Доступна валидация графа, то есть библиотека может проверить граф зависимостей еще до извлечения зависимостей. Когда регистрируем все зависимости, получаем граф, вызываем функцию проверки checkIsValid, и она проверяет, что все зарегистрировано верно, иначе бросает ошибку со ссылкой на файл и строку, где ошибка возникает, — также очень удобно. Под капотом в целом суть та же, хотя на первый взгляд кажется, что все сложнее, чем в Swinject и DIP.  Объекты под капотом хранятся в специальном объекте за протоколом: Ключ содержит информацию не только о типе, но и о файле и строке, где зависимость регистрировалась. Это нужно как раз для того, чтобы выдавать точные логи при проверке графа зависимостей. Автор библиотеки даже сравнил скорость своей библиотеки со Swinject на большом количестве регистраций(>1000) и простых графах зависимостей. По результатам этих тестов, DITranquility быстрее, чем Swinject (можно глянуть здесь). https://github.com/TinkoffCreditSystems/EasyDi 88 звезд на «Гитхабе». Отличается от предыдущих в плане синтаксиса. В отличие от предыдущих библиотек, уже не располагает к использованию себя в качестве сервис-локатора: То есть мы не регистрируем и не резолвим зависимости явно, а создаем специальный объект — Assembly, который выступает провайдером зависимостей и сам строит граф зависимостей под капотом. Все Assembly существуют в рамках некоторого контекста DIContext. Как видим, дефолтный DIContext статический и создается по умолчанию. Он контролирует граф зависимостей всех подконтрольных Assembly, то есть граф общий для всех ассембли внутри одного контекста. Сам граф зависимостей реализован с помощью словаря. Ключом в данном случае выступает имя типа Assembly + имя переменной нашей зависимости. А объект — это просто Any. Предоставляет четыре уже знакомые нам скоупа: prototype, objectGraph, lazySingleton и совсем недавно добавленный weak singleton. Кроме того, можно строить иерархии Assembly. Есть фича подмены объектов для тестирования. Библиотека довольно проста в использовании и реализации и занимает всего чуть более 200 строк кода. Кроме того, получаем потокобезопасность из коробки. Плюс нет явной фазы регистрации зависимостей, а значит, нельзя забыть зарегистрировать то, что нам понадобится. Плюсы: Просты в использовании, невысокий порог входа. В реализации относительно несложно разобраться. Минусы: Довольно легко использовать как сервис-локаторы и получить связанные с этим проблемы. Вся магия происходит в рантайме — значит, и ошибки получаем в рантайме и не у всех библиотек есть возможность валидации графа. Аффектят время запуска приложения (опять-таки из-за своей рантайм-природы). Несмотря на достаточное количество минусов, остаются самыми популярными решениями для DI в iOS. Сначала давайте разберемся, как работают подобные библиотеки. Если вкратце, вы помечаете проперти своего класса неким атрибутом или наследуете свои компоненты от определенного протокола, а библиотека (или в данном случае уже скорее фреймворк), генерирует код, который эти проперти/зависимости внедряет. И это все. Вам больше ничего делать не нужно — только использовать свои зависимости.   Если пока не стало понятно — ничего, разберемся на примерах. https://github.com/uber/needle 822 звезды на «Гитхабе». Набирающая популярность библиотека от разработчиков Uber. Работает на базе SourceKit. Главным преимуществом, по словам разработчиков, является обеспечение compile time safety кода работы для внедрения зависимостей. Контейнеры зависимостей должны наследоваться от класса Component. Зависимости DI-контейнера описываются в протоколе, который наследуется от базового протокола зависимостей Dependency и указывается в качестве generic типа самого контейнера. В примере также видим, как дочерние компоненты создаются в родительских. Очень напоминает Assembly в EasyDI, правда? Хотя под капотом они работают абсолютно по-разному. Каждый раз при сборке проекта генератор кода Needle анализирует исходный код проекта и ищет всех наследников класса Component - базового классов для DI-контейнеров. У каждого DI-контейнера есть протокол описания зависимостей. Для каждого такого протокола генератор анализирует доступность каждой зависимости путем поиска ее среди родителей контейнера. Поиск идет снизу вверх — от дочернего компонента к родительскому. Зависимость считается найденной, только если совпадают имя и тип зависимости. Если зависимость не найдена, то сборка проекта останавливается с ошибкой, в которой указывается потерянная зависимость. Это дает заявленную выше compile-time safety. Плюсы: Compile-time safety — все проверки и ошибки во время компиляции. Время запуска приложения не аффектится с ростом числа зависимостей. Потокобезопасность из коробки. Поддержка иерархии компонентов. Минусы: Пока что всего два варианта скоупа: shared (по сути — container) и prototype, но в теории можно ими обойтись. Порог входа выше, чем у сервис-локаторов, хоть и не намного. Мне самому Needle зашел. Потенциально хорошая альтернатива Swinject. Несмотря на номер последней версии v0.17.1, разработчики говорят, что библиотека production ready и уже используется во всех приложениях Uber. Более подробно о библиотеке можно почитать здесь. https://github.com/scribd/Weaver 543 звезды на «Гитхабе». Также основана на кодогенерации, использует SourceKitten, основанный на SourceKit. Но работает несколько иначе, чем Needle, а именно — через аннотации к зависимостям. Тот, кто немного знаком с миром Android и Dagger, сразу поймет, о чем речь. Аннотации в нашем случае — комментарии определенного формата или специальные property wrapper’ы. Например: или Первый вариант мне лично не нравится из-за «хрупкости» комментариев, но этот вариант был единственным до появления проперти врапперов в Swift 5.1. Второй вариант был вполне ожидаемым после их появления. Weaver сканирует код проекта в поисках аннотаций, строит граф зависимостей и генерирует все, что нужно для DI, а именно контейнер MainDependencyContainer, у которого можно получить DependencyResolver для каждой зависимости. Аннотации в Weaver бывают трех видов — register, reference и parameter: Register     Добавляет зависимость в контейнер и предоставляет доступ к зависимости.   2. Reference   Только предоставляет доступ (ссылку) к зависимости.   3. Parameter    Параметр, который будет передаваться в объект при инициализации. Все объекты, где мы добавляем аннотации, должны предоставлять стандартный init c аргументом резолвером соответствующего генерируемого типа, что выглядит как костыль: Библиотека предоставляет уже знакомые нам четыре скоупа: transient, container, weak, lazy. Плюсы: Относительно проста в использовании. Compile time safety. Умеет генерировать стабы для тестов. Минусы: Фактически библиотека принуждает нас использовать PropertyInjection. Костыльный конструктор у всех зависимостей. Порог вхождения выше, чем у needle. Неинтуитивные для понимания register/reference/parameter. Код самих объектов, в которые мы инжектим зависимости, зависит от нюансов библиотеки, а значит, поменять библиотеку на другую без изменения кода самих объектов не получится. Плюсы: Compile time safety, избавление от крашей в рантайме. Не аффектят скорость запуска приложения. Минусы: Немного сложнее для понимания, чем register/resolve-контейнеры. Объекты зависят от нюансов библиотеки (наследуют специальные протоколы или используют специальные атрибуты у полей). Попробуем сравнить производительность рассмотренных выше библиотек на растущем количестве зависимостей. Пусть каждая регистрируемая зависимость имеет от 0 до 3 зависимостей, часть которых также зависит друг от друга. Замеры будем делать на iPad 2017 (по железу — аналог iPhone 6s) iOS 13,5 (время в секундах) в релизной конфигурации(с оптимизациями). Итак, что мы имеем? DIP — аутсайдер в этой битве. Swinject и DITranquillity — самые быстрые на 100 регистрациях, но деградируют на росте регистраций до 1000. При этом DITranquillity деградирует медленнее. SwinjectAutoregistration делает свинжект еще немного медленнее. EasyDI медленнее Swinject и DITranquillity на 100 регистрациях, но зато рост регистраций слабо аффектит время его работы. И на 1000 регистраций он уже быстрее чем Swinject. Уточню, после оптимизаций в версии 4.2 производительность DITranquillity сильно выросла. До версии 4.2 производительность была прилично ниже. Казалось бы, вот оно! Мы нашли фаворитов и можно идти внедрять их в свои проекты! Расходимся! Но нет, не все так просто. Мы ведь рассматривали простой граф зависимостей. У каждой регистрируемой зависимости было от 0 до 3 зависимостей. Это довольно мало, и в крупных и сложных проектах ситуация явно хуже.  Давайте рассмотрим некоторый «худший» случай в противовес простому. Допустим, у каждой регистрируемой зависимости будет 10 своих зависимостей, которые также зависимы между собой. Как видим, ситуация сильно меняется. Swinject все еще деградирует на росте регистраций, но его время заметно меньше, чем у других библиотек, кроме DITranquillity. Видимо, работа со сложным графом в Swinject и DITranquillity оптимизирована лучше. При этом DITranquillity обгоняет Swinject при достижении уже 500 регистраций.  Отмечу, что время отдельно взятой библиотеки само по себе малоинформативно, важны лишь их значения относительно друг друга, потому что:  При тестах считалось среднее время по замерам 100 тестов подряд. И первая итерация обычно выполнялась медленнее, чем последующие. Объекты, регистрируемые в тестах, по сути — пустышки, не содержащие бизнес-логики, а значит, время их создания меньше, чем у объектов в реальных проектах. Так как проекты обычно становятся все сложнее и сложнее, добавляются все новые и новые фичи, рано или поздно рост количества регистраций и сложности графа зависимостей может привести к критически большому времени запуска приложения, это надо иметь в виду. Возможно, в таких случаях имеет смысл перейти на DI-библиотеку, основанную на кодогенерации, ведь у них вся магия происходит во время компиляции, а не в рантайме.  Но и с библиотеками на кодогенерации тоже не все так просто :) Давайте проверим, как сказывается рост числа зависимостей на времени компиляции. Рассмотрим уже упомянутые Needle и Weaver. Даже несмотря на то, что обе библиотеки работают на базе SourceKit, время компиляции отличается на порядок при большом количестве зависимостей. На этом примере можно понять, как важна оптимизация в таких библиотеках. Вот какие выводы можно сделать из вышесказанного: Многие из популярных в iOS DI-библиотек позволяют использовать их как сервис-локаторы, то есть в некотором смысле они ближе к сервис-локаторам, чем к DI-контейнерам. Работа многих DI-библиотек основана на общих принципах и инструментах, но общие принципы можно реализовать по-разному. Например, время работы контейнеров, основанных на рефлексии, очень разнится и зависит не только от количества зависимостей, но и от сложности самого графа зависимостей (ну и от реализации самого контейнера, конечно). Или, например, время компиляции для библиотек, основанных на кодогенерации, тоже очень разнится. Оптимизация работы DI-библиотек очень важна, так как аффектит запуск приложения или его время сборки. Какую библиотеку выбрать для своего проекта? Зависит от ситуации. На небольших проектах Swinject может быть хорошим вариантом. Если вы уже используете Swinject (или любую другую register/resolve библиотеку) и вас критически не устраивает время запуска приложения, присмотритесь к Needle. А еще всегда можно сделать свой DI c блэкджеком и оптимизацией. Можно это сделать довольно просто, например через фабрики, но это уже совсем другая история... https://github.com/vitalybatrakov/iOS_DI_Libs_Performance_Tests https://github.com/vitalybatrakov/iOS_DI_Libs_Compilation_Time_Tests https://martinfowler.com/articles/injection.html https://www.youtube.com/watch?v=-JGGw4SN6NA&ab_channel=AvitoTech https://jonfir.github.io/posts/ioc-ios/ http://sergeyteplyakov.blogspot.com/2013/03/di-service-locator.html http://sergeyteplyakov.blogspot.com/2014/11/di-vs-dip-vs-ioc.html https://tech.badoo.com/ru/article/411/singlton-lokator-servisov-i-testy/ https://habr.com/ru/company/joom/blog/514784/