Егор Толстой
Инженер-разработчик iOS приложений
Dependency Injection в iOS
Dependency Injection в iOS
Об авторе
• Twitter: @igrekde
• GitHub: github.com/igrekde
• Блог: etolstoy.ru/blog
Dependency Injection в iOS
Содержание
• Принцип инверсии зависимостей
• Паттерны Dependency Injection
• DI в проектах Rambler&Co
• Typhoon Framework
Dependency Injection в iOS
Принцип инверсии
зависимостей
Dependency Injection в iOS
SOLID
• S – The Single Responsibility Principle
• O – The Open-Closed Principle
• L – The Liskov Substitution Principle
• I – Interface Segregation Principle
• D – The Dependency Inversion Principle
Dependency Injection в iOS
–Роберт Мартин, “Принципы, паттерны и методики гибкой разработки”
Модули верхнего уровня не должны зависеть от модулей
нижнего уровня. И те и другие должны зависеть от
абстракций.
Абстракции не должны зависеть от деталей. Детали должны
зависеть от абстракций.
Dependency Injection в iOS
Dependency Inversion
Dependency Injection в iOS
Dependency Inversion
Dependency Injection в iOS
IoC vs DI vs DIP
• IoC – Inversion of Control
• DI – Dependency Injection
• DIP – Dependency Inversion Principle
Dependency Injection в iOS
Inversion of Control
Dependency Injection в iOS
Dependency Injection
Dependency Injection в iOS
Dependency Inversion Principle
Dependency Injection в iOS
Паттерны Dependency
Injection
Dependency Injection в iOS
Паттерны DI
• Initializer Injection
• Property Injection
• Method Injection
• Service Locator
• DI Container
Dependency Injection в iOS
Initializer Injection
@interface RCMNetworkService
- (instancetype)initWithClient:(id <RCMRPCClient>)client
configurator:(id <RCMConfigurator>)configurator;
@end
Dependency Injection в iOS
Property Injection
@interface RCMNetworkService
@property (strong, nonatomic) id <RCMEmailValidator> emailValidator;
@property (strong, nonatomic) id <RCMReachabilityManager> reachabilityManager;
@end
Dependency Injection в iOS
Method Injection
@interface RCMMailboxService
- (void)connectMailBoxWithConfigurator:(id <RCMMailBoxConfigurator>)configurator
completionBlock:(RCMErrorBlock)block;
@end
Dependency Injection в iOS
Service Locator
Dependency Injection в iOS
Service Locator
@interface MessageViewController
- (instancetype)initWithMessageService:(id <MessageService>)messageService
attachmentService:(id <AttachmentService>)attachmentService
renderer:(id <MessageRenderer>)renderer;
@end
Было:
Dependency Injection в iOS
Service Locator
@interface MessageViewController
- (instancetype)initWithServiceLocator:(id <ServiceLocator>)locator;
@end
Стало:
Dependency Injection в iOS
Service Locator
Плюсы:
• Быстро реализуется
• Централизованное управление зависимостями
Dependency Injection в iOS
Service Locator
Минусы:
• Приводит к неявным зависимостям
• Видимость хорошего дизайна
• Усложняет тестирование
Dependency Injection в iOS
DI container
• Не используется в коде напрямую
• Зависимости всех классов – явные
• Никто не заботится о создании зависимостей
Dependency Injection в iOS
Dependency Injection в
проектах Rambler&Co
Dependency Injection в iOS
Афиша Рестораны
@interface ARModalTableViewController : UIViewController <ARTableViewModelDelegate>
- (instancetype)initWithTableViewModel:(id<ARTableViewModel>)tableViewModel;
@end
Initializer Injection для UIViewController:
Dependency Injection в iOS
Афиша Рестораны
@protocol ARStoredListManager <NSObject>
- (void)setStorage:(id<ARLocalStorage>)storage;
@end
Установка зависимости через Setter:
Dependency Injection в iOS
Рамблер.Новости
@interface RDNDrawerRouterImplementation ()
……
destinationController.storyboardFactory = sourceController.storyboardFactory;
destinationController.router = [RDNFeedRouterImplementation new];
#warning Заменить fake-адаптер на боевой
destinationController.feedServiceAdapter = [RDNFakeServiceAdapterAssembly
fakeFeedDataServiceAdapterWithTopicIdentifier:topicIdentifier];
……
@end
Почти DI-контейнер:
Dependency Injection в iOS
Рамблер.WE
@interface RCIAuthorizationPresenter : NSObject <RCIAuthorizationViewOutput,
RCIAuthorizationInteractorOutput, RCIAuthorizationRouterOutput>
@property (strong, nonatomic) id <RCIAuthorizationViewInput> view;
@property (strong, nonatomic) id <RCIAuthorizationInteractorInput> interactor;
@property (strong, nonatomic) id <RCIAuthorizationRouter> router;
@end
Property Injection в модуле VIPER:
Dependency Injection в iOS
Рамблер.Почта
@interface RCMFolderSynchronizationOperation : RCMAsyncOperation<RCMRestartableOperation>
- (instancetype)initWithClient:(id <RCMRPCClient>)client
validator:(id <RCMValidator>)validator
mapper:(id <RCMMapper>)mapper;
@end
Создание операции с Initializer Injection:
Dependency Injection в iOS
Typhoon Framework
Dependency Injection в iOS
–Clarke C. Arthur
Any magic, sufficiently analyzed is indistinguishable from
technology.
Dependency Injection в iOS
Typhoon Framework
http://typhoonframework.org
Dependency Injection в iOS
Typhoon Framework
1.209
124
24 open/260 closed
0 open/60 closed
questions: 246
Последнее обновление: 09.05.15
Dependency Injection в iOS
Typhoon Framework
• Полностью нативен
• Поддерживает модульность
• Полная интеграция со Storyboard
• Initializer, Property и Method Injection
• Поддерживает circular dependencies
• Всего 3000 строчек кода
Dependency Injection в iOS
Интеграция с проектом
@interface RIAssembly : TyphoonAssembly
- (RIAppDelegate *)appDelegate;
@end
Создаем свою Assembly:
Dependency Injection в iOS
Интеграция с проектом
@implementation RIAssembly
- (RIAppDelegate *)appDelegate {
return [TyphoonDefinition withClass:[RIAppDelegate class] configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(startUpConfigurator) with:[self startUpConfigurator]];
}
}
- (id <RIStartUpConfigurator>)startUpConfigurator {
return [TyphoonDefinition withClass:[RIStartUpConfiguratorBase class]];
}
@end
Dependency Injection в iOS
Интеграция с проектом
Dependency Injection в iOS
Интеграция с проектом
Dependency Injection в iOS
Примеры
Создаем простой инстанс определенного класса:
- (id <RCMAddressBookRouter>)addressBookRouter {
return [TyphoonDefinition withClass:[RCMAddressBookRouterBase class]];
}
Dependency Injection в iOS
Примеры
Создаем инстанс класса и устанавливаем его зависимости:
- (id <RCMPopoverBuilder>)authorizationPopoverBuilder {
return [TyphoonDefinition withClass:[RCMAuthorizationPopoverBuilderBase class]
configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(storyboardBuilder)
with:[self storyboardBuilder]];
}];
}
Dependency Injection в iOS
Примеры
Настраиваем жизненный цикл объекта:
- (id <RCMLogger>)networkLogger{
return [TyphoonDefinition withClass:[RCMNetworkLoggerBase class]
configuration:^(TyphoonDefinition *definition) {
definition.scope = TyphoonScopeSingleton;
}];
}
Dependency Injection в iOS
Примеры
Создаем объект через Initializer:
- (id <RCMSettingsService>)settingsService {
return [TyphoonDefinition withClass:[RCMSettingsServiceBase class]
configuration:^(TyphoonDefinition *definition) {
[definition useInitializer:@selector(initWithClient:sessionStorage:)
parameters:^(TyphoonMethod *initializer) {
[initializer injectParameterWith:[self mailXMLRPCClient]];
[initializer injectParameterWith:[self credentialsStorage]];
}];
}];
}
Dependency Injection в iOS
Примеры
Используем Method Injection:
- (id <RCMErrorService>)errorService{
return [TyphoonDefinition withClass:[RCMErrorServiceBase class]
configuration:^(TyphoonDefinition *definition) {
[definition injectMethod:@selector(addErrorHandler:)
parameters:^(TyphoonMethod *method) {
[method injectParameterWith:[self sessionErrorHandler]];
}];
}];
}
Dependency Injection в iOS
Примеры
Настраиваем базовый Definition для всех ViewController’ов:
- (UIViewController *)baseViewController {
return [TyphoonDefinition withClass:[UIViewController class]
configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(errorService)
with:[self.serviceComponents errorService]];
[definition injectProperty:@selector(errorHandler)
with:[self baseControllerErrorHandler]];
[definition injectProperty:@selector(router)
with:[self baseRouter]];
}];
}
Dependency Injection в iOS
Примеры
Используем базовый Definition:
- (UIViewController *)userNameTableViewController {
return [TyphoonDefinition withClass:[RCMMessageCompositionViewController class]
configuration:^(TyphoonDefinition *definition) {
definition.parent = [self baseViewController];
[definition injectProperty:@selector(router)
with:[self settingsRouter]];
}];
}
Dependency Injection в iOS
Жизненный цикл
1. main.m
2. Создание UIApplication
3. Создание UIAppDelegate
4. Вызов [UIApplication setDelegate] -> Встраивается Typhoon
5. Вызов [UIAppDelegate applicationDidFinishLaunching]
Dependency Injection в iOS
TyphoonAssembly
Активация:
1. Автоматическая при наличии ключа в Info.plist
2. Ручная с использованием [TyphoonAssembly activate].
Dependency Injection в iOS
TyphoonAssembly
Активация:
1. Автоматическая при наличии ключа в Info.plist
2. Ручная с использованием [TyphoonAssembly activate].
Dependency Injection в iOS
TyphoonComponentFactory
- (id)componentForKey:(NSString *)key args:(TyphoonRuntimeArguments *)args
{
……
TyphoonDefinition *definition = [self definitionForKey:key];
……
return [self newOrScopeCachedInstanceForDefinition:definition args:args];
}
Dependency Injection в iOS
TyphoonComponentFactory
- (id)componentForType:(id)classOrProtocol
{
……
TyphoonDefinition *definition = [self definitionForType:classOrProtocol];
……
return [self newOrScopeCachedInstanceForDefinition:definition args:nil];
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
TyphoonDefinition
@interface TyphoonDefinition : NSObject <NSCopying> {
Class _type;
NSString *_key;
BOOL _processed;
TyphoonMethod *_initializer;
TyphoonMethod *_beforeInjections;
NSMutableSet *_injectedProperties;
NSMutableSet *_injectedMethods;
TyphoonMethod *_afterInjections;
TyphoonScope _scope;
TyphoonDefinition *_parent;
}
Dependency Injection в iOS
Работа со Storyboard
@implementation RCMAssembly
- (RCMMessageListTableViewController *)messageListTableViewController {
return [TyphoonDefinition
withClass:[RCMMessageListTableViewController class]];
}
@end
Dependency Injection в iOS
Работа со Storyboard
@interface TyphoonStoryboard : UIStoryboard
+ (TyphoonStoryboard *)storyboardWithName:(NSString *)name
factory:(TyphoonComponentFactory *)factory
bundle:(NSBundle *)bundleOrNil;
@end
Dependency Injection в iOS
Memory Management
• TyphoonScopeObjectGraph
• TyphoonScopePrototype
• TyphoonScopeSingleton
• TyphoonScopeLazySingleton
• TyphoonScopeWeakSingleton
Dependency Injection в iOS
Autowire
#import "TyphoonAutoInjection.h”
@interface RCINewsDetailsViewController : UIViewController
@property (strong, nonatomic) InjectedProtocol(RCINewsService) newsService;
@property (strong, nonatomic) InjectedClass(RCIThemeManager) themeManager;
@end
Dependency Injection в iOS
Autowire
Плюсы:
• Быстро реализуется
• Меньше кода в фабриках
Минусы:
• Сильная привязка к Typhoon
• Архитектура приложения не читается в фабриках
Dependency Injection в iOS
Config Injection
- (id)configurer {
return [TyphoonDefinition configDefinitionWithName:@”config.json"];
}
…..
[definition injectProperty:@selector(serviceUrl)
with:TyphoonConfig(@"service.url")];
Dependency Injection в iOS
Модульность
Dependency Injection в iOS
Модульность
NSArray *assemblies = @[serviceAssembly, networkAssembly];
[uiAssembly activateWithCollaboratingAssemblies:assemblies];
Dependency Injection в iOS
Совместная работа
Базовые сервисы:
[uiAssembly activateWithCollaboratingAssemblies:@[
[self serviceAssembly],
networkAssembly]];
Double сервисы:
[uiAssembly activateWithCollaboratingAssemblies:@[
[self doubleServiceAssembly],
networkAssembly]];
Dependency Injection в iOS
Совместная работа
#ifndef Mail_BaseAssembly_h
#define Mail_BaseAssembly_h
#define SERVICE_COMPONENTS_ASSEMBLY RCMServiceComponentsBase
#endif
Dependency Injection в iOS
Совместная работа
Dependency Injection в iOS
Тестирование
id<RCMServiceComponents> factory= [RCMServiceComponentsBase new];
TyphoonPatcher *patcher = [[TyphoonPatcher alloc] init];
[patcher patchDefinitionWithSelector:@selector(credentialsStorage) withObject:^id{
id mockCredentialsStorage = OCMProtocolMock(@protocol(RCMCredentialsStorage));
id mockSession = OCMClassMock([RCMSession class]);
OCMStub([mockSession rsid]).andReturn(@"123");
OCMStub([mockCredentialsStorage currentSession]).andReturn(mockSession);
return mockCredentialsStorage;
}];
[factory attachPostProcessor:patcher];
self.pushService = [factory pushService];
Dependency Injection в iOS
Мифы
• Высокий порог вхождения
• Очень сложный дебаггинг
• Если Typhoon перестанут поддерживать, из проекта его не
выпилить
• Но… там же свиззлинг!
• Зачем мне Typhoon, когда я могу написать свой велосипед?
Dependency Injection в iOS
Рекомендации
• Разбивайте свои фабрики не только вертикально, но и
горизонтально
• Разбивайте фабрики по модулям заранее
• Покрывайте фабрики тестами
Dependency Injection в iOS
Другие библиотеки
Dependency Injection в iOS
Objection
930
106
13 open/42 closed
1 open/35 closed
By Atomic Object
http://objection-framework.org
Последнее обновление: 29.04.15
Dependency Injection в iOS
Objection
@interface Car : NSObject {
Engine *engine;
Brakes *brakes;
}
@property(nonatomic, strong) Engine *engine;
@property(nonatomic, strong) Brakes *brakes;
@implementation Car
objection_requires(@"engine", @"brakes")
@synthesize engine, brakes;
@end
Dependency Injection в iOS
Objection
@interface MyAppModule : JSObjectionModule { }
@end
@implementation MyAppModule
- (void)configure {
[self bind:[UIApplication sharedApplication] toClass:[UIApplication class]];
[self bindClass:[MyAPIService class] toProtocol:@protocol(APIService)];
}
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
JSObjectionInjector *injector = [JSObjection createInjector:[[MyAppModule alloc] init]];
[JSObjection setDefaultInjector:injector];
}
Dependency Injection в iOS
Objection
Плюсы:
• Легко и просто бьется на модули
• Легковесная
• Простая для освоения
Dependency Injection в iOS
Objection
Минусы:
• Все зависимости назначаются практически вручную
• Слишком сильная интеграция с кодом приложения
• Всего два вида объектов: прототип и синглтон
Dependency Injection в iOS
BloodMagic
216
30
2 open/6 closed
0 open/12 closed
By AlexDenisov
https://github.com/railsware/BloodMagic
Последнее обновление: 12.05.15
Dependency Injection в iOS
BloodMagic
@interface ViewController : UIViewController <BMInjectable>
@property (nonatomic, strong, bm_injectable) MessageService *messageService;
@end
…..
BMInitializer *initializer = [BMInitializer injectableInitializer];
initializer.propertyClass = [MessageService class];
initializer.initializer = ^id (id sender) {
return [[MessageService alloc] initWithViewController:sender];
};
[initializer registerInitializer];
Dependency Injection в iOS
BloodMagic
Плюсы:
• Еще более легковесная библиотека
Минусы:
• Не позволяет создавать и управлять графами объектов
• Нет никаких плюшек DI-фреймворков
Dependency Injection в iOS
СПАСИБО!

Rambler.iOS #3: Dependency Injection в iOS

  • 1.
    Егор Толстой Инженер-разработчик iOSприложений Dependency Injection в iOS
  • 2.
    Dependency Injection вiOS Об авторе • Twitter: @igrekde • GitHub: github.com/igrekde • Блог: etolstoy.ru/blog
  • 3.
    Dependency Injection вiOS Содержание • Принцип инверсии зависимостей • Паттерны Dependency Injection • DI в проектах Rambler&Co • Typhoon Framework
  • 4.
    Dependency Injection вiOS Принцип инверсии зависимостей
  • 5.
    Dependency Injection вiOS SOLID • S – The Single Responsibility Principle • O – The Open-Closed Principle • L – The Liskov Substitution Principle • I – Interface Segregation Principle • D – The Dependency Inversion Principle
  • 6.
    Dependency Injection вiOS –Роберт Мартин, “Принципы, паттерны и методики гибкой разработки” Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
  • 7.
    Dependency Injection вiOS Dependency Inversion
  • 8.
    Dependency Injection вiOS Dependency Inversion
  • 9.
    Dependency Injection вiOS IoC vs DI vs DIP • IoC – Inversion of Control • DI – Dependency Injection • DIP – Dependency Inversion Principle
  • 10.
    Dependency Injection вiOS Inversion of Control
  • 11.
    Dependency Injection вiOS Dependency Injection
  • 12.
    Dependency Injection вiOS Dependency Inversion Principle
  • 13.
    Dependency Injection вiOS Паттерны Dependency Injection
  • 14.
    Dependency Injection вiOS Паттерны DI • Initializer Injection • Property Injection • Method Injection • Service Locator • DI Container
  • 15.
    Dependency Injection вiOS Initializer Injection @interface RCMNetworkService - (instancetype)initWithClient:(id <RCMRPCClient>)client configurator:(id <RCMConfigurator>)configurator; @end
  • 16.
    Dependency Injection вiOS Property Injection @interface RCMNetworkService @property (strong, nonatomic) id <RCMEmailValidator> emailValidator; @property (strong, nonatomic) id <RCMReachabilityManager> reachabilityManager; @end
  • 17.
    Dependency Injection вiOS Method Injection @interface RCMMailboxService - (void)connectMailBoxWithConfigurator:(id <RCMMailBoxConfigurator>)configurator completionBlock:(RCMErrorBlock)block; @end
  • 18.
    Dependency Injection вiOS Service Locator
  • 19.
    Dependency Injection вiOS Service Locator @interface MessageViewController - (instancetype)initWithMessageService:(id <MessageService>)messageService attachmentService:(id <AttachmentService>)attachmentService renderer:(id <MessageRenderer>)renderer; @end Было:
  • 20.
    Dependency Injection вiOS Service Locator @interface MessageViewController - (instancetype)initWithServiceLocator:(id <ServiceLocator>)locator; @end Стало:
  • 21.
    Dependency Injection вiOS Service Locator Плюсы: • Быстро реализуется • Централизованное управление зависимостями
  • 22.
    Dependency Injection вiOS Service Locator Минусы: • Приводит к неявным зависимостям • Видимость хорошего дизайна • Усложняет тестирование
  • 23.
    Dependency Injection вiOS DI container • Не используется в коде напрямую • Зависимости всех классов – явные • Никто не заботится о создании зависимостей
  • 24.
    Dependency Injection вiOS Dependency Injection в проектах Rambler&Co
  • 25.
    Dependency Injection вiOS Афиша Рестораны @interface ARModalTableViewController : UIViewController <ARTableViewModelDelegate> - (instancetype)initWithTableViewModel:(id<ARTableViewModel>)tableViewModel; @end Initializer Injection для UIViewController:
  • 26.
    Dependency Injection вiOS Афиша Рестораны @protocol ARStoredListManager <NSObject> - (void)setStorage:(id<ARLocalStorage>)storage; @end Установка зависимости через Setter:
  • 27.
    Dependency Injection вiOS Рамблер.Новости @interface RDNDrawerRouterImplementation () …… destinationController.storyboardFactory = sourceController.storyboardFactory; destinationController.router = [RDNFeedRouterImplementation new]; #warning Заменить fake-адаптер на боевой destinationController.feedServiceAdapter = [RDNFakeServiceAdapterAssembly fakeFeedDataServiceAdapterWithTopicIdentifier:topicIdentifier]; …… @end Почти DI-контейнер:
  • 28.
    Dependency Injection вiOS Рамблер.WE @interface RCIAuthorizationPresenter : NSObject <RCIAuthorizationViewOutput, RCIAuthorizationInteractorOutput, RCIAuthorizationRouterOutput> @property (strong, nonatomic) id <RCIAuthorizationViewInput> view; @property (strong, nonatomic) id <RCIAuthorizationInteractorInput> interactor; @property (strong, nonatomic) id <RCIAuthorizationRouter> router; @end Property Injection в модуле VIPER:
  • 29.
    Dependency Injection вiOS Рамблер.Почта @interface RCMFolderSynchronizationOperation : RCMAsyncOperation<RCMRestartableOperation> - (instancetype)initWithClient:(id <RCMRPCClient>)client validator:(id <RCMValidator>)validator mapper:(id <RCMMapper>)mapper; @end Создание операции с Initializer Injection:
  • 30.
    Dependency Injection вiOS Typhoon Framework
  • 31.
    Dependency Injection вiOS –Clarke C. Arthur Any magic, sufficiently analyzed is indistinguishable from technology.
  • 32.
    Dependency Injection вiOS Typhoon Framework http://typhoonframework.org
  • 33.
    Dependency Injection вiOS Typhoon Framework 1.209 124 24 open/260 closed 0 open/60 closed questions: 246 Последнее обновление: 09.05.15
  • 34.
    Dependency Injection вiOS Typhoon Framework • Полностью нативен • Поддерживает модульность • Полная интеграция со Storyboard • Initializer, Property и Method Injection • Поддерживает circular dependencies • Всего 3000 строчек кода
  • 35.
    Dependency Injection вiOS Интеграция с проектом @interface RIAssembly : TyphoonAssembly - (RIAppDelegate *)appDelegate; @end Создаем свою Assembly:
  • 36.
    Dependency Injection вiOS Интеграция с проектом @implementation RIAssembly - (RIAppDelegate *)appDelegate { return [TyphoonDefinition withClass:[RIAppDelegate class] configuration:^(TyphoonDefinition *definition) { [definition injectProperty:@selector(startUpConfigurator) with:[self startUpConfigurator]]; } } - (id <RIStartUpConfigurator>)startUpConfigurator { return [TyphoonDefinition withClass:[RIStartUpConfiguratorBase class]]; } @end
  • 37.
    Dependency Injection вiOS Интеграция с проектом
  • 38.
    Dependency Injection вiOS Интеграция с проектом
  • 39.
    Dependency Injection вiOS Примеры Создаем простой инстанс определенного класса: - (id <RCMAddressBookRouter>)addressBookRouter { return [TyphoonDefinition withClass:[RCMAddressBookRouterBase class]]; }
  • 40.
    Dependency Injection вiOS Примеры Создаем инстанс класса и устанавливаем его зависимости: - (id <RCMPopoverBuilder>)authorizationPopoverBuilder { return [TyphoonDefinition withClass:[RCMAuthorizationPopoverBuilderBase class] configuration:^(TyphoonDefinition *definition) { [definition injectProperty:@selector(storyboardBuilder) with:[self storyboardBuilder]]; }]; }
  • 41.
    Dependency Injection вiOS Примеры Настраиваем жизненный цикл объекта: - (id <RCMLogger>)networkLogger{ return [TyphoonDefinition withClass:[RCMNetworkLoggerBase class] configuration:^(TyphoonDefinition *definition) { definition.scope = TyphoonScopeSingleton; }]; }
  • 42.
    Dependency Injection вiOS Примеры Создаем объект через Initializer: - (id <RCMSettingsService>)settingsService { return [TyphoonDefinition withClass:[RCMSettingsServiceBase class] configuration:^(TyphoonDefinition *definition) { [definition useInitializer:@selector(initWithClient:sessionStorage:) parameters:^(TyphoonMethod *initializer) { [initializer injectParameterWith:[self mailXMLRPCClient]]; [initializer injectParameterWith:[self credentialsStorage]]; }]; }]; }
  • 43.
    Dependency Injection вiOS Примеры Используем Method Injection: - (id <RCMErrorService>)errorService{ return [TyphoonDefinition withClass:[RCMErrorServiceBase class] configuration:^(TyphoonDefinition *definition) { [definition injectMethod:@selector(addErrorHandler:) parameters:^(TyphoonMethod *method) { [method injectParameterWith:[self sessionErrorHandler]]; }]; }]; }
  • 44.
    Dependency Injection вiOS Примеры Настраиваем базовый Definition для всех ViewController’ов: - (UIViewController *)baseViewController { return [TyphoonDefinition withClass:[UIViewController class] configuration:^(TyphoonDefinition *definition) { [definition injectProperty:@selector(errorService) with:[self.serviceComponents errorService]]; [definition injectProperty:@selector(errorHandler) with:[self baseControllerErrorHandler]]; [definition injectProperty:@selector(router) with:[self baseRouter]]; }]; }
  • 45.
    Dependency Injection вiOS Примеры Используем базовый Definition: - (UIViewController *)userNameTableViewController { return [TyphoonDefinition withClass:[RCMMessageCompositionViewController class] configuration:^(TyphoonDefinition *definition) { definition.parent = [self baseViewController]; [definition injectProperty:@selector(router) with:[self settingsRouter]]; }]; }
  • 46.
    Dependency Injection вiOS Жизненный цикл 1. main.m 2. Создание UIApplication 3. Создание UIAppDelegate 4. Вызов [UIApplication setDelegate] -> Встраивается Typhoon 5. Вызов [UIAppDelegate applicationDidFinishLaunching]
  • 47.
    Dependency Injection вiOS TyphoonAssembly Активация: 1. Автоматическая при наличии ключа в Info.plist 2. Ручная с использованием [TyphoonAssembly activate].
  • 48.
    Dependency Injection вiOS TyphoonAssembly Активация: 1. Автоматическая при наличии ключа в Info.plist 2. Ручная с использованием [TyphoonAssembly activate].
  • 49.
    Dependency Injection вiOS TyphoonComponentFactory - (id)componentForKey:(NSString *)key args:(TyphoonRuntimeArguments *)args { …… TyphoonDefinition *definition = [self definitionForKey:key]; …… return [self newOrScopeCachedInstanceForDefinition:definition args:args]; }
  • 50.
    Dependency Injection вiOS TyphoonComponentFactory - (id)componentForType:(id)classOrProtocol { …… TyphoonDefinition *definition = [self definitionForType:classOrProtocol]; …… return [self newOrScopeCachedInstanceForDefinition:definition args:nil]; }
  • 51.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 52.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 53.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 54.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 55.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 56.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 57.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 58.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 59.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 60.
    Dependency Injection вiOS TyphoonDefinition @interface TyphoonDefinition : NSObject <NSCopying> { Class _type; NSString *_key; BOOL _processed; TyphoonMethod *_initializer; TyphoonMethod *_beforeInjections; NSMutableSet *_injectedProperties; NSMutableSet *_injectedMethods; TyphoonMethod *_afterInjections; TyphoonScope _scope; TyphoonDefinition *_parent; }
  • 61.
    Dependency Injection вiOS Работа со Storyboard @implementation RCMAssembly - (RCMMessageListTableViewController *)messageListTableViewController { return [TyphoonDefinition withClass:[RCMMessageListTableViewController class]]; } @end
  • 62.
    Dependency Injection вiOS Работа со Storyboard @interface TyphoonStoryboard : UIStoryboard + (TyphoonStoryboard *)storyboardWithName:(NSString *)name factory:(TyphoonComponentFactory *)factory bundle:(NSBundle *)bundleOrNil; @end
  • 63.
    Dependency Injection вiOS Memory Management • TyphoonScopeObjectGraph • TyphoonScopePrototype • TyphoonScopeSingleton • TyphoonScopeLazySingleton • TyphoonScopeWeakSingleton
  • 64.
    Dependency Injection вiOS Autowire #import "TyphoonAutoInjection.h” @interface RCINewsDetailsViewController : UIViewController @property (strong, nonatomic) InjectedProtocol(RCINewsService) newsService; @property (strong, nonatomic) InjectedClass(RCIThemeManager) themeManager; @end
  • 65.
    Dependency Injection вiOS Autowire Плюсы: • Быстро реализуется • Меньше кода в фабриках Минусы: • Сильная привязка к Typhoon • Архитектура приложения не читается в фабриках
  • 66.
    Dependency Injection вiOS Config Injection - (id)configurer { return [TyphoonDefinition configDefinitionWithName:@”config.json"]; } ….. [definition injectProperty:@selector(serviceUrl) with:TyphoonConfig(@"service.url")];
  • 67.
    Dependency Injection вiOS Модульность
  • 68.
    Dependency Injection вiOS Модульность NSArray *assemblies = @[serviceAssembly, networkAssembly]; [uiAssembly activateWithCollaboratingAssemblies:assemblies];
  • 69.
    Dependency Injection вiOS Совместная работа Базовые сервисы: [uiAssembly activateWithCollaboratingAssemblies:@[ [self serviceAssembly], networkAssembly]]; Double сервисы: [uiAssembly activateWithCollaboratingAssemblies:@[ [self doubleServiceAssembly], networkAssembly]];
  • 70.
    Dependency Injection вiOS Совместная работа #ifndef Mail_BaseAssembly_h #define Mail_BaseAssembly_h #define SERVICE_COMPONENTS_ASSEMBLY RCMServiceComponentsBase #endif
  • 71.
    Dependency Injection вiOS Совместная работа
  • 72.
    Dependency Injection вiOS Тестирование id<RCMServiceComponents> factory= [RCMServiceComponentsBase new]; TyphoonPatcher *patcher = [[TyphoonPatcher alloc] init]; [patcher patchDefinitionWithSelector:@selector(credentialsStorage) withObject:^id{ id mockCredentialsStorage = OCMProtocolMock(@protocol(RCMCredentialsStorage)); id mockSession = OCMClassMock([RCMSession class]); OCMStub([mockSession rsid]).andReturn(@"123"); OCMStub([mockCredentialsStorage currentSession]).andReturn(mockSession); return mockCredentialsStorage; }]; [factory attachPostProcessor:patcher]; self.pushService = [factory pushService];
  • 73.
    Dependency Injection вiOS Мифы • Высокий порог вхождения • Очень сложный дебаггинг • Если Typhoon перестанут поддерживать, из проекта его не выпилить • Но… там же свиззлинг! • Зачем мне Typhoon, когда я могу написать свой велосипед?
  • 74.
    Dependency Injection вiOS Рекомендации • Разбивайте свои фабрики не только вертикально, но и горизонтально • Разбивайте фабрики по модулям заранее • Покрывайте фабрики тестами
  • 75.
    Dependency Injection вiOS Другие библиотеки
  • 76.
    Dependency Injection вiOS Objection 930 106 13 open/42 closed 1 open/35 closed By Atomic Object http://objection-framework.org Последнее обновление: 29.04.15
  • 77.
    Dependency Injection вiOS Objection @interface Car : NSObject { Engine *engine; Brakes *brakes; } @property(nonatomic, strong) Engine *engine; @property(nonatomic, strong) Brakes *brakes; @implementation Car objection_requires(@"engine", @"brakes") @synthesize engine, brakes; @end
  • 78.
    Dependency Injection вiOS Objection @interface MyAppModule : JSObjectionModule { } @end @implementation MyAppModule - (void)configure { [self bind:[UIApplication sharedApplication] toClass:[UIApplication class]]; [self bindClass:[MyAPIService class] toProtocol:@protocol(APIService)]; } @end - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { JSObjectionInjector *injector = [JSObjection createInjector:[[MyAppModule alloc] init]]; [JSObjection setDefaultInjector:injector]; }
  • 79.
    Dependency Injection вiOS Objection Плюсы: • Легко и просто бьется на модули • Легковесная • Простая для освоения
  • 80.
    Dependency Injection вiOS Objection Минусы: • Все зависимости назначаются практически вручную • Слишком сильная интеграция с кодом приложения • Всего два вида объектов: прототип и синглтон
  • 81.
    Dependency Injection вiOS BloodMagic 216 30 2 open/6 closed 0 open/12 closed By AlexDenisov https://github.com/railsware/BloodMagic Последнее обновление: 12.05.15
  • 82.
    Dependency Injection вiOS BloodMagic @interface ViewController : UIViewController <BMInjectable> @property (nonatomic, strong, bm_injectable) MessageService *messageService; @end ….. BMInitializer *initializer = [BMInitializer injectableInitializer]; initializer.propertyClass = [MessageService class]; initializer.initializer = ^id (id sender) { return [[MessageService alloc] initWithViewController:sender]; }; [initializer registerInitializer];
  • 83.
    Dependency Injection вiOS BloodMagic Плюсы: • Еще более легковесная библиотека Минусы: • Не позволяет создавать и управлять графами объектов • Нет никаких плюшек DI-фреймворков
  • 84.
    Dependency Injection вiOS СПАСИБО!