dependency injection в ios
TRANSCRIPT
Егор Толстой Инженер-разработчик 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
СПАСИБО!