$30 off During Our Annual Pro Sale. View Details »

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. Егор Толстой
    Инженер-разработчик iOS приложений
    Dependency Injection в iOS

    View Slide

  2. Dependency Injection в iOS
    Об авторе
    • Twitter: @igrekde
    • GitHub: github.com/igrekde
    • Блог: etolstoy.ru/blog

    View Slide

  3. Dependency Injection в iOS
    Содержание
    • Принцип инверсии зависимостей
    • Паттерны Dependency Injection
    • DI в проектах Rambler&Co
    • Typhoon Framework

    View Slide

  4. Dependency Injection в iOS
    Принцип инверсии
    зависимостей

    View Slide

  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

    View Slide

  6. Dependency Injection в iOS
    –Роберт Мартин, “Принципы, паттерны и методики гибкой разработки”
    Модули верхнего уровня не должны зависеть от модулей
    нижнего уровня. И те и другие должны зависеть от
    абстракций.
    Абстракции не должны зависеть от деталей. Детали должны
    зависеть от абстракций.

    View Slide

  7. Dependency Injection в iOS
    Dependency Inversion

    View Slide

  8. Dependency Injection в iOS
    Dependency Inversion

    View Slide

  9. Dependency Injection в iOS
    IoC vs DI vs DIP
    • IoC – Inversion of Control
    • DI – Dependency Injection
    • DIP – Dependency Inversion Principle

    View Slide

  10. Dependency Injection в iOS
    Inversion of Control

    View Slide

  11. Dependency Injection в iOS
    Dependency Injection

    View Slide

  12. Dependency Injection в iOS
    Dependency Inversion Principle

    View Slide

  13. Dependency Injection в iOS
    Паттерны Dependency
    Injection

    View Slide

  14. Dependency Injection в iOS
    Паттерны DI
    • Initializer Injection
    • Property Injection
    • Method Injection
    • Service Locator
    • DI Container

    View Slide

  15. Dependency Injection в iOS
    Initializer Injection
    @interface RCMNetworkService
    - (instancetype)initWithClient:(id )client
    configurator:(id )configurator;
    @end

    View Slide

  16. Dependency Injection в iOS
    Property Injection
    @interface RCMNetworkService
    @property (strong, nonatomic) id emailValidator;
    @property (strong, nonatomic) id reachabilityManager;
    @end

    View Slide

  17. Dependency Injection в iOS
    Method Injection
    @interface RCMMailboxService
    - (void)connectMailBoxWithConfigurator:(id )configurator
    completionBlock:(RCMErrorBlock)block;
    @end

    View Slide

  18. Dependency Injection в iOS
    Service Locator

    View Slide

  19. Dependency Injection в iOS
    Service Locator
    @interface MessageViewController
    - (instancetype)initWithMessageService:(id )messageService
    attachmentService:(id )attachmentService
    renderer:(id )renderer;
    @end
    Было:

    View Slide

  20. Dependency Injection в iOS
    Service Locator
    @interface MessageViewController
    - (instancetype)initWithServiceLocator:(id )locator;
    @end
    Стало:

    View Slide

  21. Dependency Injection в iOS
    Service Locator
    Плюсы:
    • Быстро реализуется
    • Централизованное управление зависимостями

    View Slide

  22. Dependency Injection в iOS
    Service Locator
    Минусы:
    • Приводит к неявным зависимостям
    • Видимость хорошего дизайна
    • Усложняет тестирование

    View Slide

  23. Dependency Injection в iOS
    DI container
    • Не используется в коде напрямую
    • Зависимости всех классов – явные
    • Никто не заботится о создании зависимостей

    View Slide

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

    View Slide

  25. Dependency Injection в iOS
    Афиша Рестораны
    @interface ARModalTableViewController : UIViewController
    - (instancetype)initWithTableViewModel:(id)tableViewModel;
    @end
    Initializer Injection для UIViewController:

    View Slide

  26. Dependency Injection в iOS
    Афиша Рестораны
    @protocol ARStoredListManager
    - (void)setStorage:(id)storage;
    @end
    Установка зависимости через Setter:

    View Slide

  27. Dependency Injection в iOS
    Рамблер.Новости
    @interface RDNDrawerRouterImplementation ()
    ……
    destinationController.storyboardFactory = sourceController.storyboardFactory;
    destinationController.router = [RDNFeedRouterImplementation new];
    #warning Заменить fake-адаптер на боевой
    destinationController.feedServiceAdapter = [RDNFakeServiceAdapterAssembly
    fakeFeedDataServiceAdapterWithTopicIdentifier:topicIdentifier];
    ……
    @end
    Почти DI-контейнер:

    View Slide

  28. Dependency Injection в iOS
    Рамблер.WE
    @interface RCIAuthorizationPresenter : NSObject RCIAuthorizationInteractorOutput, RCIAuthorizationRouterOutput>
    @property (strong, nonatomic) id view;
    @property (strong, nonatomic) id interactor;
    @property (strong, nonatomic) id router;
    @end
    Property Injection в модуле VIPER:

    View Slide

  29. Dependency Injection в iOS
    Рамблер.Почта
    @interface RCMFolderSynchronizationOperation : RCMAsyncOperation
    - (instancetype)initWithClient:(id )client
    validator:(id )validator
    mapper:(id )mapper;
    @end
    Создание операции с Initializer Injection:

    View Slide

  30. Dependency Injection в iOS
    Typhoon Framework

    View Slide

  31. Dependency Injection в iOS
    –Clarke C. Arthur
    Any magic, sufficiently analyzed is indistinguishable from
    technology.

    View Slide

  32. Dependency Injection в iOS
    Typhoon Framework
    http://typhoonframework.org

    View Slide

  33. Dependency Injection в iOS
    Typhoon Framework
    1.209
    124
    24 open/260 closed
    0 open/60 closed
    questions: 246
    Последнее обновление: 09.05.15

    View Slide

  34. Dependency Injection в iOS
    Typhoon Framework
    • Полностью нативен
    • Поддерживает модульность
    • Полная интеграция со Storyboard
    • Initializer, Property и Method Injection
    • Поддерживает circular dependencies
    • Всего 3000 строчек кода

    View Slide

  35. Dependency Injection в iOS
    Интеграция с проектом
    @interface RIAssembly : TyphoonAssembly
    - (RIAppDelegate *)appDelegate;
    @end
    Создаем свою Assembly:

    View Slide

  36. Dependency Injection в iOS
    Интеграция с проектом
    @implementation RIAssembly
    - (RIAppDelegate *)appDelegate {
    return [TyphoonDefinition withClass:[RIAppDelegate class] configuration:^(TyphoonDefinition *definition) {
    [definition injectProperty:@selector(startUpConfigurator) with:[self startUpConfigurator]];
    }
    }
    - (id )startUpConfigurator {
    return [TyphoonDefinition withClass:[RIStartUpConfiguratorBase class]];
    }
    @end

    View Slide

  37. Dependency Injection в iOS
    Интеграция с проектом

    View Slide

  38. Dependency Injection в iOS
    Интеграция с проектом

    View Slide

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

    View Slide

  40. Dependency Injection в iOS
    Примеры
    Создаем инстанс класса и устанавливаем его зависимости:
    - (id )authorizationPopoverBuilder {
    return [TyphoonDefinition withClass:[RCMAuthorizationPopoverBuilderBase class]
    configuration:^(TyphoonDefinition *definition) {
    [definition injectProperty:@selector(storyboardBuilder)
    with:[self storyboardBuilder]];
    }];
    }

    View Slide

  41. Dependency Injection в iOS
    Примеры
    Настраиваем жизненный цикл объекта:
    - (id )networkLogger{
    return [TyphoonDefinition withClass:[RCMNetworkLoggerBase class]
    configuration:^(TyphoonDefinition *definition) {
    definition.scope = TyphoonScopeSingleton;
    }];
    }

    View Slide

  42. Dependency Injection в iOS
    Примеры
    Создаем объект через Initializer:
    - (id )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]];
    }];
    }];
    }

    View Slide

  43. Dependency Injection в iOS
    Примеры
    Используем Method Injection:
    - (id )errorService{
    return [TyphoonDefinition withClass:[RCMErrorServiceBase class]
    configuration:^(TyphoonDefinition *definition) {
    [definition injectMethod:@selector(addErrorHandler:)
    parameters:^(TyphoonMethod *method) {
    [method injectParameterWith:[self sessionErrorHandler]];
    }];
    }];
    }

    View Slide

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

    View Slide

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

    View Slide

  46. Dependency Injection в iOS
    Жизненный цикл
    1. main.m
    2. Создание UIApplication
    3. Создание UIAppDelegate
    4. Вызов [UIApplication setDelegate] -> Встраивается Typhoon
    5. Вызов [UIAppDelegate applicationDidFinishLaunching]

    View Slide

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

    View Slide

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

    View Slide

  49. Dependency Injection в iOS
    TyphoonComponentFactory
    - (id)componentForKey:(NSString *)key args:(TyphoonRuntimeArguments *)args
    {
    ……
    TyphoonDefinition *definition = [self definitionForKey:key];
    ……
    return [self newOrScopeCachedInstanceForDefinition:definition args:args];
    }

    View Slide

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

    View Slide

  51. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  52. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  53. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  54. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  55. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  56. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  57. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  58. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  59. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

  60. Dependency Injection в iOS
    TyphoonDefinition
    @interface TyphoonDefinition : NSObject {
    Class _type;
    NSString *_key;
    BOOL _processed;
    TyphoonMethod *_initializer;
    TyphoonMethod *_beforeInjections;
    NSMutableSet *_injectedProperties;
    NSMutableSet *_injectedMethods;
    TyphoonMethod *_afterInjections;
    TyphoonScope _scope;
    TyphoonDefinition *_parent;
    }

    View Slide

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

    View Slide

  62. Dependency Injection в iOS
    Работа со Storyboard
    @interface TyphoonStoryboard : UIStoryboard
    + (TyphoonStoryboard *)storyboardWithName:(NSString *)name
    factory:(TyphoonComponentFactory *)factory
    bundle:(NSBundle *)bundleOrNil;
    @end

    View Slide

  63. Dependency Injection в iOS
    Memory Management
    • TyphoonScopeObjectGraph
    • TyphoonScopePrototype
    • TyphoonScopeSingleton
    • TyphoonScopeLazySingleton
    • TyphoonScopeWeakSingleton

    View Slide

  64. Dependency Injection в iOS
    Autowire
    #import "TyphoonAutoInjection.h”
    @interface RCINewsDetailsViewController : UIViewController
    @property (strong, nonatomic) InjectedProtocol(RCINewsService) newsService;
    @property (strong, nonatomic) InjectedClass(RCIThemeManager) themeManager;
    @end

    View Slide

  65. Dependency Injection в iOS
    Autowire
    Плюсы:
    • Быстро реализуется
    • Меньше кода в фабриках
    Минусы:
    • Сильная привязка к Typhoon
    • Архитектура приложения не читается в фабриках

    View Slide

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

    View Slide

  67. Dependency Injection в iOS
    Модульность

    View Slide

  68. Dependency Injection в iOS
    Модульность
    NSArray *assemblies = @[serviceAssembly, networkAssembly];
    [uiAssembly activateWithCollaboratingAssemblies:assemblies];

    View Slide

  69. Dependency Injection в iOS
    Совместная работа
    Базовые сервисы:
    [uiAssembly activateWithCollaboratingAssemblies:@[
    [self serviceAssembly],
    networkAssembly]];
    Double сервисы:
    [uiAssembly activateWithCollaboratingAssemblies:@[
    [self doubleServiceAssembly],
    networkAssembly]];

    View Slide

  70. Dependency Injection в iOS
    Совместная работа
    #ifndef Mail_BaseAssembly_h
    #define Mail_BaseAssembly_h
    #define SERVICE_COMPONENTS_ASSEMBLY RCMServiceComponentsBase
    #endif

    View Slide

  71. Dependency Injection в iOS
    Совместная работа

    View Slide

  72. Dependency Injection в iOS
    Тестирование
    id 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];

    View Slide

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

    View Slide

  74. Dependency Injection в iOS
    Рекомендации
    • Разбивайте свои фабрики не только вертикально, но и
    горизонтально
    • Разбивайте фабрики по модулям заранее
    • Покрывайте фабрики тестами

    View Slide

  75. Dependency Injection в iOS
    Другие библиотеки

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  80. Dependency Injection в iOS
    Objection
    Минусы:
    • Все зависимости назначаются практически вручную
    • Слишком сильная интеграция с кодом приложения
    • Всего два вида объектов: прототип и синглтон

    View Slide

  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

    View Slide

  82. Dependency Injection в iOS
    BloodMagic
    @interface ViewController : UIViewController
    @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];

    View Slide

  83. Dependency Injection в iOS
    BloodMagic
    Плюсы:
    • Еще более легковесная библиотека
    Минусы:
    • Не позволяет создавать и управлять графами объектов
    • Нет никаких плюшек DI-фреймворков

    View Slide

  84. Dependency Injection в iOS
    СПАСИБО!

    View Slide