Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injection в iOS

Dependency Injection в iOS

Выступление на Rambler.iOS #3.
Видео: https://www.youtube.com/watch?v=LO59z3fjc9k

Egor Tolstoy

May 15, 2015
Tweet

More Decks by Egor Tolstoy

Other Decks in Technology

Transcript

  1. Dependency Injection в iOS Об авторе • Twitter: @igrekde •

    GitHub: github.com/igrekde • Блог: etolstoy.ru/blog
  2. Dependency Injection в iOS Содержание • Принцип инверсии зависимостей •

    Паттерны Dependency Injection • DI в проектах Rambler&Co • Typhoon Framework
  3. 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
  4. Dependency Injection в iOS –Роберт Мартин, “Принципы, паттерны и методики

    гибкой разработки” Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
  5. Dependency Injection в iOS IoC vs DI vs DIP •

    IoC – Inversion of Control • DI – Dependency Injection • DIP – Dependency Inversion Principle
  6. Dependency Injection в iOS Паттерны DI • Initializer Injection •

    Property Injection • Method Injection • Service Locator • DI Container
  7. Dependency Injection в iOS Initializer Injection @interface RCMNetworkService - (instancetype)initWithClient:(id

    <RCMRPCClient>)client configurator:(id <RCMConfigurator>)configurator; @end
  8. Dependency Injection в iOS Property Injection @interface RCMNetworkService @property (strong,

    nonatomic) id <RCMEmailValidator> emailValidator; @property (strong, nonatomic) id <RCMReachabilityManager> reachabilityManager; @end
  9. Dependency Injection в iOS Method Injection @interface RCMMailboxService - (void)connectMailBoxWithConfigurator:(id

    <RCMMailBoxConfigurator>)configurator completionBlock:(RCMErrorBlock)block; @end
  10. Dependency Injection в iOS Service Locator @interface MessageViewController - (instancetype)initWithMessageService:(id

    <MessageService>)messageService attachmentService:(id <AttachmentService>)attachmentService renderer:(id <MessageRenderer>)renderer; @end Было:
  11. Dependency Injection в iOS Service Locator Плюсы: • Быстро реализуется

    • Централизованное управление зависимостями
  12. Dependency Injection в iOS Service Locator Минусы: • Приводит к

    неявным зависимостям • Видимость хорошего дизайна • Усложняет тестирование
  13. Dependency Injection в iOS DI container • Не используется в

    коде напрямую • Зависимости всех классов – явные • Никто не заботится о создании зависимостей
  14. Dependency Injection в iOS Афиша Рестораны @interface ARModalTableViewController : UIViewController

    <ARTableViewModelDelegate> - (instancetype)initWithTableViewModel:(id<ARTableViewModel>)tableViewModel; @end Initializer Injection для UIViewController:
  15. Dependency Injection в iOS Афиша Рестораны @protocol ARStoredListManager <NSObject> -

    (void)setStorage:(id<ARLocalStorage>)storage; @end Установка зависимости через Setter:
  16. Dependency Injection в iOS Рамблер.Новости @interface RDNDrawerRouterImplementation () …… destinationController.storyboardFactory

    = sourceController.storyboardFactory; destinationController.router = [RDNFeedRouterImplementation new]; #warning Заменить fake-адаптер на боевой destinationController.feedServiceAdapter = [RDNFakeServiceAdapterAssembly fakeFeedDataServiceAdapterWithTopicIdentifier:topicIdentifier]; …… @end Почти DI-контейнер:
  17. 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:
  18. Dependency Injection в iOS Рамблер.Почта @interface RCMFolderSynchronizationOperation : RCMAsyncOperation<RCMRestartableOperation> -

    (instancetype)initWithClient:(id <RCMRPCClient>)client validator:(id <RCMValidator>)validator mapper:(id <RCMMapper>)mapper; @end Создание операции с Initializer Injection:
  19. Dependency Injection в iOS –Clarke C. Arthur Any magic, sufficiently

    analyzed is indistinguishable from technology.
  20. Dependency Injection в iOS Typhoon Framework 1.209 124 24 open/260

    closed 0 open/60 closed questions: 246 Последнее обновление: 09.05.15
  21. Dependency Injection в iOS Typhoon Framework • Полностью нативен •

    Поддерживает модульность • Полная интеграция со Storyboard • Initializer, Property и Method Injection • Поддерживает circular dependencies • Всего 3000 строчек кода
  22. Dependency Injection в iOS Интеграция с проектом @interface RIAssembly :

    TyphoonAssembly - (RIAppDelegate *)appDelegate; @end Создаем свою Assembly:
  23. 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
  24. Dependency Injection в iOS Примеры Создаем простой инстанс определенного класса:

    - (id <RCMAddressBookRouter>)addressBookRouter { return [TyphoonDefinition withClass:[RCMAddressBookRouterBase class]]; }
  25. Dependency Injection в iOS Примеры Создаем инстанс класса и устанавливаем

    его зависимости: - (id <RCMPopoverBuilder>)authorizationPopoverBuilder { return [TyphoonDefinition withClass:[RCMAuthorizationPopoverBuilderBase class] configuration:^(TyphoonDefinition *definition) { [definition injectProperty:@selector(storyboardBuilder) with:[self storyboardBuilder]]; }]; }
  26. Dependency Injection в iOS Примеры Настраиваем жизненный цикл объекта: -

    (id <RCMLogger>)networkLogger{ return [TyphoonDefinition withClass:[RCMNetworkLoggerBase class] configuration:^(TyphoonDefinition *definition) { definition.scope = TyphoonScopeSingleton; }]; }
  27. 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]]; }]; }]; }
  28. 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]]; }]; }]; }
  29. 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]]; }]; }
  30. 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]]; }]; }
  31. Dependency Injection в iOS Жизненный цикл 1. main.m 2. Создание

    UIApplication 3. Создание UIAppDelegate 4. Вызов [UIApplication setDelegate] -> Встраивается Typhoon 5. Вызов [UIAppDelegate applicationDidFinishLaunching]
  32. Dependency Injection в iOS TyphoonAssembly Активация: 1. Автоматическая при наличии

    ключа в Info.plist 2. Ручная с использованием [TyphoonAssembly activate].
  33. Dependency Injection в iOS TyphoonAssembly Активация: 1. Автоматическая при наличии

    ключа в Info.plist 2. Ручная с использованием [TyphoonAssembly activate].
  34. Dependency Injection в iOS TyphoonComponentFactory - (id)componentForKey:(NSString *)key args:(TyphoonRuntimeArguments *)args

    { …… TyphoonDefinition *definition = [self definitionForKey:key]; …… return [self newOrScopeCachedInstanceForDefinition:definition args:args]; }
  35. Dependency Injection в iOS TyphoonComponentFactory - (id)componentForType:(id)classOrProtocol { …… TyphoonDefinition

    *definition = [self definitionForType:classOrProtocol]; …… return [self newOrScopeCachedInstanceForDefinition:definition args:nil]; }
  36. 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; }
  37. 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; }
  38. 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; }
  39. 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; }
  40. 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; }
  41. 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; }
  42. 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; }
  43. 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; }
  44. 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; }
  45. 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; }
  46. Dependency Injection в iOS Работа со Storyboard @implementation RCMAssembly -

    (RCMMessageListTableViewController *)messageListTableViewController { return [TyphoonDefinition withClass:[RCMMessageListTableViewController class]]; } @end
  47. Dependency Injection в iOS Работа со Storyboard @interface TyphoonStoryboard :

    UIStoryboard + (TyphoonStoryboard *)storyboardWithName:(NSString *)name factory:(TyphoonComponentFactory *)factory bundle:(NSBundle *)bundleOrNil; @end
  48. Dependency Injection в iOS Memory Management • TyphoonScopeObjectGraph • TyphoonScopePrototype

    • TyphoonScopeSingleton • TyphoonScopeLazySingleton • TyphoonScopeWeakSingleton
  49. Dependency Injection в iOS Autowire #import "TyphoonAutoInjection.h” @interface RCINewsDetailsViewController :

    UIViewController @property (strong, nonatomic) InjectedProtocol(RCINewsService) newsService; @property (strong, nonatomic) InjectedClass(RCIThemeManager) themeManager; @end
  50. Dependency Injection в iOS Autowire Плюсы: • Быстро реализуется •

    Меньше кода в фабриках Минусы: • Сильная привязка к Typhoon • Архитектура приложения не читается в фабриках
  51. Dependency Injection в iOS Config Injection - (id)configurer { return

    [TyphoonDefinition configDefinitionWithName:@”config.json"]; } ….. [definition injectProperty:@selector(serviceUrl) with:TyphoonConfig(@"service.url")];
  52. Dependency Injection в iOS Совместная работа Базовые сервисы: [uiAssembly activateWithCollaboratingAssemblies:@[

    [self serviceAssembly], networkAssembly]]; Double сервисы: [uiAssembly activateWithCollaboratingAssemblies:@[ [self doubleServiceAssembly], networkAssembly]];
  53. 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];
  54. Dependency Injection в iOS Мифы • Высокий порог вхождения •

    Очень сложный дебаггинг • Если Typhoon перестанут поддерживать, из проекта его не выпилить • Но… там же свиззлинг! • Зачем мне Typhoon, когда я могу написать свой велосипед?
  55. Dependency Injection в iOS Рекомендации • Разбивайте свои фабрики не

    только вертикально, но и горизонтально • Разбивайте фабрики по модулям заранее • Покрывайте фабрики тестами
  56. Dependency Injection в iOS Objection 930 106 13 open/42 closed

    1 open/35 closed By Atomic Object http://objection-framework.org Последнее обновление: 29.04.15
  57. 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
  58. 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]; }
  59. Dependency Injection в iOS Objection Плюсы: • Легко и просто

    бьется на модули • Легковесная • Простая для освоения
  60. Dependency Injection в iOS Objection Минусы: • Все зависимости назначаются

    практически вручную • Слишком сильная интеграция с кодом приложения • Всего два вида объектов: прототип и синглтон
  61. Dependency Injection в iOS BloodMagic 216 30 2 open/6 closed

    0 open/12 closed By AlexDenisov https://github.com/railsware/BloodMagic Последнее обновление: 12.05.15
  62. 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];
  63. Dependency Injection в iOS BloodMagic Плюсы: • Еще более легковесная

    библиотека Минусы: • Не позволяет создавать и управлять графами объектов • Нет никаких плюшек DI-фреймворков