84
Егор Толстой Инженер-разработчик iOS приложений Dependency Injection в iOS

Dependency Injection в iOS

Embed Size (px)

Citation preview

Page 1: Dependency Injection в iOS

Егор Толстой Инженер-разработчик iOS приложений

Dependency Injection в iOS

Page 2: Dependency Injection в iOS

Dependency Injection в iOS

Об авторе

• Twitter: @igrekde

• GitHub: github.com/igrekde

• Блог: etolstoy.ru/blog

Page 3: Dependency Injection в iOS

Dependency Injection в iOS

Содержание

• Принцип инверсии зависимостей

• Паттерны Dependency Injection

• DI в проектах Rambler&Co

• Typhoon Framework

Page 4: Dependency Injection в iOS

Dependency Injection в iOS

Принцип инверсии зависимостей

Page 5: 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

Page 6: Dependency Injection в iOS

Dependency Injection в iOS

–Роберт Мартин, “Принципы, паттерны и методики гибкой разработки”

Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от

абстракций.

Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Page 7: Dependency Injection в iOS

Dependency Injection в iOS

Dependency Inversion

Page 8: Dependency Injection в iOS

Dependency Injection в iOS

Dependency Inversion

Page 9: Dependency Injection в iOS

Dependency Injection в iOS

IoC vs DI vs DIP

• IoC – Inversion of Control

• DI – Dependency Injection

• DIP – Dependency Inversion Principle

Page 10: Dependency Injection в iOS

Dependency Injection в iOS

Inversion of Control

Page 11: Dependency Injection в iOS

Dependency Injection в iOS

Dependency Injection

Page 12: Dependency Injection в iOS

Dependency Injection в iOS

Dependency Inversion Principle

Page 13: Dependency Injection в iOS

Dependency Injection в iOS

Паттерны Dependency Injection

Page 14: Dependency Injection в iOS

Dependency Injection в iOS

Паттерны DI• Initializer Injection

• Property Injection

• Method Injection

• Service Locator

• DI Container

Page 15: Dependency Injection в iOS

Dependency Injection в iOS

Initializer Injection

@interface RCMNetworkService

- (instancetype)initWithClient:(id <RCMRPCClient>)client configurator:(id <RCMConfigurator>)configurator;

@end

Page 16: Dependency Injection в iOS

Dependency Injection в iOS

Property Injection

@interface RCMNetworkService

@property (strong, nonatomic) id <RCMEmailValidator> emailValidator; @property (strong, nonatomic) id <RCMReachabilityManager> reachabilityManager;

@end

Page 17: Dependency Injection в iOS

Dependency Injection в iOS

Method Injection

@interface RCMMailboxService

- (void)connectMailBoxWithConfigurator:(id <RCMMailBoxConfigurator>)configurator completionBlock:(RCMErrorBlock)block;

@end

Page 18: Dependency Injection в iOS

Dependency Injection в iOS

Service Locator

Page 19: Dependency Injection в iOS

Dependency Injection в iOS

Service Locator

@interface MessageViewController

- (instancetype)initWithMessageService:(id <MessageService>)messageService attachmentService:(id <AttachmentService>)attachmentService renderer:(id <MessageRenderer>)renderer;

@end

Было:

Page 20: Dependency Injection в iOS

Dependency Injection в iOS

Service Locator

@interface MessageViewController

- (instancetype)initWithServiceLocator:(id <ServiceLocator>)locator;

@end

Стало:

Page 21: Dependency Injection в iOS

Dependency Injection в iOS

Service Locator

Плюсы:

• Быстро реализуется

• Централизованное управление зависимостями

Page 22: Dependency Injection в iOS

Dependency Injection в iOS

Service Locator

Минусы:

• Приводит к неявным зависимостям

• Видимость хорошего дизайна

• Усложняет тестирование

Page 23: Dependency Injection в iOS

Dependency Injection в iOS

DI container

• Не используется в коде напрямую

• Зависимости всех классов – явные

• Никто не заботится о создании зависимостей

Page 24: Dependency Injection в iOS

Dependency Injection в iOS

Dependency Injection в проектах Rambler&Co

Page 25: Dependency Injection в iOS

Dependency Injection в iOS

Афиша Рестораны

@interface ARModalTableViewController : UIViewController <ARTableViewModelDelegate>

- (instancetype)initWithTableViewModel:(id<ARTableViewModel>)tableViewModel;

@end

Initializer Injection для UIViewController:

Page 26: Dependency Injection в iOS

Dependency Injection в iOS

Афиша Рестораны

@protocol ARStoredListManager <NSObject>

- (void)setStorage:(id<ARLocalStorage>)storage;

@end

Установка зависимости через Setter:

Page 27: Dependency Injection в iOS

Dependency Injection в iOS

Рамблер.Новости

@interface RDNDrawerRouterImplementation () …… destinationController.storyboardFactory = sourceController.storyboardFactory; destinationController.router = [RDNFeedRouterImplementation new];

#warning Заменить fake-адаптер на боевой destinationController.feedServiceAdapter = [RDNFakeServiceAdapterAssembly fakeFeedDataServiceAdapterWithTopicIdentifier:topicIdentifier]; …… @end

Почти DI-контейнер:

Page 28: Dependency Injection в iOS

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:

Page 29: Dependency Injection в iOS

Dependency Injection в iOS

Рамблер.Почта

@interface RCMFolderSynchronizationOperation : RCMAsyncOperation<RCMRestartableOperation>

- (instancetype)initWithClient:(id <RCMRPCClient>)client validator:(id <RCMValidator>)validator

mapper:(id <RCMMapper>)mapper;

@end

Создание операции с Initializer Injection:

Page 30: Dependency Injection в iOS

Dependency Injection в iOS

Typhoon Framework

Page 31: Dependency Injection в iOS

Dependency Injection в iOS

–Clarke C. Arthur

Any magic, sufficiently analyzed is indistinguishable from technology.

Page 32: Dependency Injection в iOS

Dependency Injection в iOS

Typhoon Framework

http://typhoonframework.org

Page 33: Dependency Injection в iOS

Dependency Injection в iOS

Typhoon Framework 1.209

124

24 open/260 closed

0 open/60 closed

questions: 246

Последнее обновление: 09.05.15

Page 34: Dependency Injection в iOS

Dependency Injection в iOS

Typhoon Framework• Полностью нативен

• Поддерживает модульность

• Полная интеграция со Storyboard

• Initializer, Property и Method Injection

• Поддерживает circular dependencies

• Всего 3000 строчек кода

Page 35: Dependency Injection в iOS

Dependency Injection в iOS

Интеграция с проектом

@interface RIAssembly : TyphoonAssembly

- (RIAppDelegate *)appDelegate;

@end

Создаем свою Assembly:

Page 36: Dependency Injection в iOS

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

Page 37: Dependency Injection в iOS

Dependency Injection в iOS

Интеграция с проектом

Page 38: Dependency Injection в iOS

Dependency Injection в iOS

Интеграция с проектом

Page 39: Dependency Injection в iOS

Dependency Injection в iOS

Примеры

Создаем простой инстанс определенного класса:

- (id <RCMAddressBookRouter>)addressBookRouter { return [TyphoonDefinition withClass:[RCMAddressBookRouterBase class]]; }

Page 40: Dependency Injection в iOS

Dependency Injection в iOS

Примеры

Создаем инстанс класса и устанавливаем его зависимости:

- (id <RCMPopoverBuilder>)authorizationPopoverBuilder { return [TyphoonDefinition withClass:[RCMAuthorizationPopoverBuilderBase class] configuration:^(TyphoonDefinition *definition) { [definition injectProperty:@selector(storyboardBuilder) with:[self storyboardBuilder]]; }]; }

Page 41: Dependency Injection в iOS

Dependency Injection в iOS

Примеры

Настраиваем жизненный цикл объекта:

- (id <RCMLogger>)networkLogger{ return [TyphoonDefinition withClass:[RCMNetworkLoggerBase class] configuration:^(TyphoonDefinition *definition) { definition.scope = TyphoonScopeSingleton; }]; }

Page 42: Dependency Injection в iOS

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]];

}]; }]; }

Page 43: Dependency Injection в iOS

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]]; }]; }]; }

Page 44: Dependency Injection в iOS

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]]; }];

}

Page 45: Dependency Injection в iOS

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]]; }]; }

Page 46: Dependency Injection в iOS

Dependency Injection в iOS

Жизненный цикл1. main.m

2. Создание UIApplication

3. Создание UIAppDelegate

4. Вызов [UIApplication setDelegate] -> Встраивается Typhoon

5. Вызов [UIAppDelegate applicationDidFinishLaunching]

Page 47: Dependency Injection в iOS

Dependency Injection в iOS

TyphoonAssemblyАктивация:

1. Автоматическая при наличии ключа в Info.plist

2. Ручная с использованием [TyphoonAssembly activate].

Page 48: Dependency Injection в iOS

Dependency Injection в iOS

TyphoonAssemblyАктивация:

1. Автоматическая при наличии ключа в Info.plist

2. Ручная с использованием [TyphoonAssembly activate].

Page 49: Dependency Injection в iOS

Dependency Injection в iOS

TyphoonComponentFactory

- (id)componentForKey:(NSString *)key args:(TyphoonRuntimeArguments *)args { …… TyphoonDefinition *definition = [self definitionForKey:key]; …… return [self newOrScopeCachedInstanceForDefinition:definition args:args]; }

Page 50: Dependency Injection в iOS

Dependency Injection в iOS

TyphoonComponentFactory

- (id)componentForType:(id)classOrProtocol { …… TyphoonDefinition *definition = [self definitionForType:classOrProtocol]; …… return [self newOrScopeCachedInstanceForDefinition:definition args:nil]; }

Page 51: Dependency Injection в iOS

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; }

Page 52: Dependency Injection в iOS

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; }

Page 53: Dependency Injection в iOS

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; }

Page 54: Dependency Injection в iOS

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; }

Page 55: Dependency Injection в iOS

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; }

Page 56: Dependency Injection в iOS

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; }

Page 57: Dependency Injection в iOS

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; }

Page 58: Dependency Injection в iOS

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; }

Page 59: Dependency Injection в iOS

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; }

Page 60: Dependency Injection в iOS

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; }

Page 61: Dependency Injection в iOS

Dependency Injection в iOS

Работа со Storyboard

@implementation RCMAssembly

- (RCMMessageListTableViewController *)messageListTableViewController {

return [TyphoonDefinition

withClass:[RCMMessageListTableViewController class]];

}

@end

Page 62: Dependency Injection в iOS

Dependency Injection в iOS

Работа со Storyboard

@interface TyphoonStoryboard : UIStoryboard

+ (TyphoonStoryboard *)storyboardWithName:(NSString *)name factory:(TyphoonComponentFactory *)factory

bundle:(NSBundle *)bundleOrNil;

@end

Page 63: Dependency Injection в iOS

Dependency Injection в iOS

Memory Management

• TyphoonScopeObjectGraph

• TyphoonScopePrototype

• TyphoonScopeSingleton

• TyphoonScopeLazySingleton

• TyphoonScopeWeakSingleton

Page 64: Dependency Injection в iOS

Dependency Injection в iOS

Autowire

#import "TyphoonAutoInjection.h”

@interface RCINewsDetailsViewController : UIViewController

@property (strong, nonatomic) InjectedProtocol(RCINewsService) newsService; @property (strong, nonatomic) InjectedClass(RCIThemeManager) themeManager;

@end

Page 65: Dependency Injection в iOS

Dependency Injection в iOS

AutowireПлюсы:

• Быстро реализуется

• Меньше кода в фабриках

Минусы:

• Сильная привязка к Typhoon

• Архитектура приложения не читается в фабриках

Page 66: Dependency Injection в iOS

Dependency Injection в iOS

Config Injection

- (id)configurer { return [TyphoonDefinition configDefinitionWithName:@”config.json"]; } ….. [definition injectProperty:@selector(serviceUrl) with:TyphoonConfig(@"service.url")];

Page 67: Dependency Injection в iOS

Dependency Injection в iOS

Модульность

Page 68: Dependency Injection в iOS

Dependency Injection в iOS

Модульность

NSArray *assemblies = @[serviceAssembly, networkAssembly];

[uiAssembly activateWithCollaboratingAssemblies:assemblies];

Page 69: Dependency Injection в iOS

Dependency Injection в iOS

Совместная работаБазовые сервисы: [uiAssembly activateWithCollaboratingAssemblies:@[

[self serviceAssembly],

networkAssembly]];

Double сервисы: [uiAssembly activateWithCollaboratingAssemblies:@[

[self doubleServiceAssembly],

networkAssembly]];

Page 70: Dependency Injection в iOS

Dependency Injection в iOS

Совместная работа

#ifndef Mail_BaseAssembly_h

#define Mail_BaseAssembly_h

#define SERVICE_COMPONENTS_ASSEMBLY RCMServiceComponentsBase

#endif

Page 71: Dependency Injection в iOS

Dependency Injection в iOS

Совместная работа

Page 72: 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];

Page 73: Dependency Injection в iOS

Dependency Injection в iOS

Мифы• Высокий порог вхождения

• Очень сложный дебаггинг

• Если Typhoon перестанут поддерживать, из проекта его не выпилить

• Но… там же свиззлинг!

• Зачем мне Typhoon, когда я могу написать свой велосипед?

Page 74: Dependency Injection в iOS

Dependency Injection в iOS

Рекомендации• Разбивайте свои фабрики не только вертикально, но и горизонтально

• Разбивайте фабрики по модулям заранее

• Покрывайте фабрики тестами

Page 75: Dependency Injection в iOS

Dependency Injection в iOS

Другие библиотеки

Page 76: 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

Page 77: Dependency Injection в iOS

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

Page 78: Dependency Injection в iOS

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];

}

Page 79: Dependency Injection в iOS

Dependency Injection в iOS

ObjectionПлюсы:

• Легко и просто бьется на модули

• Легковесная

• Простая для освоения

Page 80: Dependency Injection в iOS

Dependency Injection в iOS

ObjectionМинусы:

• Все зависимости назначаются практически вручную

• Слишком сильная интеграция с кодом приложения

• Всего два вида объектов: прототип и синглтон

Page 81: Dependency Injection в iOS

Dependency Injection в iOS

BloodMagic 216

30

2 open/6 closed

0 open/12 closed

By AlexDenisov

https://github.com/railsware/BloodMagic

Последнее обновление: 12.05.15

Page 82: Dependency Injection в iOS

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];

Page 83: Dependency Injection в iOS

Dependency Injection в iOS

BloodMagicПлюсы:

• Еще более легковесная библиотека

Минусы:

• Не позволяет создавать и управлять графами объектов

• Нет никаких плюшек DI-фреймворков

Page 84: Dependency Injection в iOS

Dependency Injection в iOS

СПАСИБО!