Перед миграцией взвесьте все за и против, так как изменения достаточно колоссальные. При этом новый более лаконичный синтаксис с улучшенными проверками, возможно, вам не нужен. Основная особенность этого обновления, что API в дальнейшем не будет претерпевать серьезных изменений, будут добавляться новые возможности, но старые изменяться не будут.
Если приведенные ниже возможности не являются для вас критичными, и проект не планируется развиваться еще очень долго, то не стоит проводить миграцию. Но миграцию стоит провести, если вы планируете использовать swift4 и выше, так как версии 2.x.x не будут переходить на 4 версию языка.
Благодаря этому обновлению стала возможным сделать отрисовку графа зависимостей вашего приложения. На текущий момент весь граф хранится только в памяти, но после перехода на swift4 все силы будут направлены на отдельный проект и добавление функционала, для сохранения и визуализации графа зависимостей.
Было сосредоточено много сил на ускорении работы библиотеки, это дало увеличение скорости регистрации в 35 раз и увеличение скорости получения объекта в 25 раз. Теперь чтобы в релизе выйти за пределы в 1 секунду при инициализации DI надо иметь более 4к компонент в программе, это почти не реальная цифра. Для примера проект с 200к строчками кода имеет порядка 300 компонентов. Для сложных кейсов скорость выполнения не измерялась, но если посмотреть на динамику становится понятно, что скорость увеличилась на не меньшие цифры. Библиотека стала работать на 15%-100% быстрее Swinject (чем больше проект, тем больше процент) на синтетических тестах, что не может не радовать.
В силу написанных ниже изменений, стало неудобно использовать регистрацию/получение по имени, но при этом теги стали неотъемлемой частью библиотеки. У тегов есть преимущество - сложнее допустить опечатку, но и минус - это тип в приложении, а значит, к этому типу должны иметь доступ в разных местах системы.
- Произошло слияние DIComponent и DIComponentBuilder. Теперь для регистрации и для получения объекта используется один и тот же объект. Сделано это в рамках упрощения синтаксиса, а также ускорения работы библиотеки. Помимо этого слияние дает возможность во время исполнения легко подключать дополнительный код.
- Функция
buildудалена. Но это не значит, что вы оставлены самим себе. Теперь появилась более сильная функцияvalid- она проверяет валидность графа связей. Эта функция может гарантировать отсутствие падений во время исполнения из-за некорректных регистраций. Но так как эта проверка основана на валидации графа, она не может гарантировать, что вызовы функцииresolveиз вашего кода пройдут успешно. - Функция
registerстала более простой. Теперь не надо писатьtype:. Как и раньше функция на вход принимает или Тип или Метод инициализации. resolveсильно упрощен - он теперь всегда возвращает тот тип, который вы от него требуете. Если типа не будет найдено, то программа упадет. Но не бойтесь - опционалы теперь работают на полную мощность. Не надо больше писатьtry?или особый синтаксис, все делается автоматически. НО! ImplicitlyUnwrappedOptional не является опционалом - если этого типа нет, то программа также упадет.- Остался всего один сокращенный синтаксис
*containerкоторый автоматически делаетresolve. - В силу чего появились два вспомогательных метода:
by(tag:on:)иmany(:). Для получения объекта по имени надо, как и раньше вызвать функциюresolve(name:)
- Удалилось разделение указание типа, имени, тега. Теперь это одна функция, которая принимает на вход: тип, тип+имя, тип+тег. Причина: На старом синтаксисе было не понятно, как связываются теги, имена и типы между собой. Теперь это явно очевидно.
- Убрано обязательное условие на использование
checkу оператораas. Но я настоятельно рекомендую его использовать, чтобы обезопасить себя. setDefault()переименован вdefault()- Убраны методы:
initial. В этом месте произошло понижение возможностей - теперь можно указать только один метод инициализации, и он указывается при вызове функцииregister. Это позволило сократить избыточность синтаксиса, и сделать его более кратким, правда, в обмен на одну мало нужную возможность. - Убрана возможность передачи runtime аргументов. Эта возможность мало используемая, но при этом очень не продуманная: например мне нужно передать аргументы через 3 инициализации в DI, что становится невозможным. Проблема с мульти инициализацией и передачи аргументов будет решена в дальнейшем не стандартным решением.
- Убраны все
.manualи.optionalметоды. Для построения графа эти методы неприемлемы. Все для чего раньше надо было применять метод с.manualтеперь можно решить штатными средствами. Единственное исключение - инициализации, которым нужны именованные параметры. Но с тегами все нормально. - У функции
injectionдля внедрения зависимости по параметру появился булев флаг:injection(cycle:). Старая реализация позволяющая работать с циклами имела ряд недостатков, которые были устранены в новой версии. Но при этом стало необходимо явно указывать места, где возможны циклы. В принципе функцияvalid()не пройдет если будут циклы, которые невозможно создать и напишет ошибку в логи - какие именно циклы надо "разорвать". Разрыв цикла - это указание у функцииtrue, то есть:injection(cycle: true) { $0.property = $1 }. Библиотека способна сама находить места, где стоит поставить данное значение, но не делает это в угоду скорости работы. - Теги и имена больше не создают уникальные объекты, для объектов со временем жизни синглетон. Если надо чтобы они были разные, то всегда можно написать рядом еще одну регистрацию, то есть еще один компонент.
DIComponentпереименован вDIPart. Так как компонент является именем, используемым в других DI для обозначения другой сущности. Более того новое название лучше отображает его предназначение - это объединение компонентов в одну сущность, то есть это какая-то "часть" системы.DIModuleпереименовано вDIFramework. Библиотека написана для apple систем, не стоит отклоняться от их него способа именования.- Синтаксис объявления
DIFrameworkперестал отличаться отDIPart. То есть это два протокола отличающихся лишь названием и предназначением. DIFrameworkиDIPartтеперь имеют статическую функциюload(container:). Соответственно регистрируются теперь не экземпляры этих классов, а сами классы.- Функция
registerразделена. Теперь через эту функции возможно зарегистрировать только новый тип. Чтобы добавить в контейнерDIFrameworkилиDIPartпоявились две функции:append(part:)иappend(framework:) - Полностью удалена область видимости у
DIPart. Библиотека теперь сама ищет нужный компонент. При поиске учитывается наличиеdefaultу компоненты и ее иерархичное размещение в bundle. - Для задания иерархии между фреймворками появилась функция
import. Эта функция действует по аналогии директивы swift import - она лишь говорит, где можно искать реализации. Использовать эту функцию стоит только внутри реализацииDIFramework, в противном случае библиотека не гарантирует валидности.
- Полностью убраны ошибки. Теперь все ошибки/предупреждения и информация попадают в логи.
- Логгер стал важной частью системы - если что-то работает не как планировалось, в первую очередь стоит заглянуть в логи особенно на уровень error и warning.
- Логгирование стало иерархическим. То есть в логах появилась табуляция, которую при желании можно отключить.
- Убран протокол для логирования. Теперь для этих целей можно задать функцию.
- Изменены уровни логирования на:
error,warning,info,none. И стало возможность фильтровать логи по уровню.
- Убраны отдельные функции для регистрации ViewController-ов - теперь они регистрируются также как и обычные типы.
- Создание
DIStoryboardтеперь происходит с помощью статической функцииcreate(name:bundle:)илиcreate(name:bundle:container:). - Но стоит регистрировать storyboard отдельной функцией:
registerStoryboard(name:bundle:)- это упрощает дальнейшую работу и!... - Появилась поддержка storyboardReference. Но storyboard на которые ведут ссылки обязаны быть зарегистрированы с помощью функции
registerStoryboard(name:bundle:)
Так как DIFramework и DIPart теперь являются классами, которые не надо инициализировать, то из-за этого был переработан синтаксис сканирования:
- Теперь для этого придется создавать целый класс. Безусловно, синтаксически это стало менее красиво, но зато быстрее с точки зрения исполнения.
- Внутри класса можно переопределить predicate или bundle. При этом для
DIScanFrameworkbundle указать нельзя, в силу его ненужности. - Предикаты также как и раньше остались двух видов: по имени и по типу. При этом если его не переопределить или сделать nil, то будут выданы все фреймворки/части.
DIScannedостался без изменений и несет тот же смысл.
- Убран
perScopeв силу его ненужности. perDependencyразделен на два:prototypeиobjectGraph.prototype- не зависимо ни от чего объект будет создаваться всегда новый.objectGraph- объект также всегда создается новый, но единожды в рамках одного разрешения зависимостей. Это время жизни стоит указывать в случае циклов на объекты, с которых будет начинаться цикл. В случае если у вас есть цикл, состоящий из объектов с разными временами жизни, и есть хотя бы один объект сobjectGraph, то будет написано предупреждение, что возможны проблемы.
- Теперь вся библиотека идет целиком - нет деления на части. Это позволило сократить код и улучшить производительность
Дальше будут примеры, как было раньше и как стало:
БЫЛО:
let builder = DIContainerBuilder()
builder.register(type: YourType.self)
.initial(YourType.init)
.injection{ $0.parameter = $1 }
builder.register(module: YourModule())
builder.register(component: YourComponent())
let container = try! builder.build()СТАЛО:
let container = DIContainer()
builder.register(YourType.init)
.injection{ $0.parameter = $1 }
container.append(framework: YourFramework.self)
container.append(part: YourPart.self)
if !container.valid() { // можно проверять только в дебаге, а в релизе упустить для экономии времени загрузки
fatalError("Your write incorrect dependencies graph")
}БЫЛО:
builder.register(type: YourType.self)
.initialNotNecessary()
builder.register(vc: YourViewControllerType.self)СТАЛО:
container.register(YourType.self)
container.register(YourViewControllerType.self)БЫЛО:
builder.register(type: YourType.self)
.initial{ YourType(pTag: $0.resolve(tag: yourTag), pMany: $0.resolveMany()) }
.injection(.manual) { $1.parameterTag = $0.resolve(tag: yourTag) }
.injection(.manual) { $1.parameterMany = $0.resolveMany() }СТАЛО:
container.register{ YourType(pTag: by(tag: YourTag.self, on: $0), pMany: many($1)) }
.injection { $0.parameterTag = by(tag: YourTag.self, on: $1) }
.injection { $0.parameterMany = many($1) }БЫЛО:
builder.register(type: YourType.self)
.initial{ YourType(pName: $0.resolve(name: "yourName")) }
builder.register(type: YourTypeParameter.self)
.injection(.manual) { $1.parameterName = $0.resolve(name: "YourName") }СТАЛО:
// Регистрация с указанием имени в методе инициализации не поддерживаем. Но есть Теги.
container.register(YourTypeParameter.self)
.injection(name: "YourName") { $0.parameterName = $1 }БЫЛО:
builder.register(type: Class.init)
.set(name: "n1")
.set(name: "n2")
.lifetime(.single)
let a = container.resolve(name: "n1")
let b = container.resolve(name: "n2")
// (a != b) == trueСТАЛО:
container.register(Class.init)
.as(Class.self, name: "n1")
.as(Class.self, name: "n2")
.lifetime(.single)
let a = container.resolve(name: "n1")
let b = container.resolve(name: "n2")
// (a == b) == trueБЫЛО:
builder.register(type: YourType.self)
.initial{ YourType(pOptional: try? $0.resolve()) }
.injection(.optional) { $0.parameterOpt = $1 }
builder.register(type: YourType2.self)
.initial{ YourType(pOptional: try? $0.resolve()) }
.injection(.manual) { $1.parameterOpt = try? $0.resolver() }СТАЛО:
container.register(YourType.init)
.injection { $0.parameterOpt = $1 }
container.register{ YourType2(pOptional: $0) }
.injection { $0.parameterOpt = $1 }БЫЛО:
builder.register(type: YourType.self)
.as(YourProtocol.self).unsafe()
.as(YourProtocol.self).check{$0}
.as(YourPorotocl.self){$0}СТАЛО:
container.register(YourType.self)
.as(YourProtocol.self)
.as(check: YourProtocol.self){$0}БЫЛО:
builder.register(type: YourType.self)
.as(YourProtocol.self)
.as(YourProtocol2.self)
.set(name: "YourName")
.set(tag: yourTag)
// Данный синтаксис генерировал все возможные варианты, то есть: YourProtocol+"YourName", YourProtocol+yourTag, YourProtocol2+"YourName", YourProtocol2+yourTag
// В большинстве случае это не совсем то чего хочется, поэтому синтаксис был переделан, на более однозначныйСТАЛО:
container.register(YourType.self)
.as(YourProtocol.self, name: "YourName")
.as(YourProtocol2.self, tag: YourTag.self)
// тут мы явно написали две пары, хотя можем написать еще пару строчек, и создать еще варианты, но скорей всего этого не требуется.БЫЛО:
class YourComponent: DIComponent {
func load(builder: DIContainerBuilder) { ... }
}
class YourModule: DIModule {
var components: [DIComponent] = { return [ YourComponent() ] }
var dependencies: [DIModule] = { return [YourOtherModule() ] }
}СТАЛО:
class YourPart: DIPart {
static func load(container: DIContainer) { ... }
}
class YourFramework: DIFramework {
static func load(container: DIContainer) {
container.append(part: YourPart.self)
container.append(framework: YourOtherFramework.self) // возможно эта строчка и не нужна - достаточно чтобы ктото один раз прикрепил загрузку framework
container.import(YourOtherFramework.self)
}
}БЫЛО:
builder.register(A.init(b:))
builder.register(B.init)
.injection{ $0.a = $1 }СТАЛО:
builder.register(A.init(b:))
.lifeTime(.objectGraph)
builder.register(B.init)
.injection(cycle: true) { $0.a = $1 }
.lifeTime(.objectGraph)Усложнение синтаксиса было сделано из-за ускорения работы библиотеки.
БЫЛО:
builder.register(component: DIScanComponent(predicateByName: { $0.contains("component") }, in: Bundle(for: YourClass.self)))СТАЛО:
class YourScanPart: DIScanPart {
override class var predicate: Predicate? { return .name({ $0.contains("part") }) }
override class var bundle: Bundle? { return Bundle(for: YourClass.self) }
}
container.append(part: YourScanPart.self)БЫЛО:
let obj1: Obj1Type = try! container.resolver()
let obj1Opt: Obj1Type? = try? container.resolver()
let obj2: Obj2Type = *container
let obj2Opt: Obj2Type? = *?container
let obj3: Obj3Type = try! container.resolve(tag: yourTag)
let obj4: Obj4Type = try! container.resolve(name: "name")
let objs: [ObjType] = try! container.resolveMany()СТАЛО:
let obj1: Obj1Type = container.resolver()
let obj1Opt: Obj1Type? = container.resolver()
let obj2: Obj2Type = *container
let obj2Opt: Obj2Type? = *container
let obj3: Obj3Type = container.resolve(tag: YourTag.self) // by(tag: YourTag.self, on: *container)
let obj4: Obj4Type = container.resolve(name: "name")
let objs: [ObjType] = container.resolveMany() // many(*container)Более подробно о новом синтаксисе и возможностях можно почитать в документации.
Чтобы установить и начать использовать новую версию, обновите библиотеку до версии выше или равной 3.0.0.