Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Dependency Injection в Objective-C с Магией и Кровью

Anna | 2.07.2014 | нет комментариев
С всяким днем iOS приложения становятся все больше массивными, в следствие чего одного MVC становится немного.

Мы видим все огромнее и огромнее классов разного назначения: логика выносится в сервисы, модели оборачиваются декораторами, огромные представления разбиваются на больше мелкие части. И самое основное, что в этом случае у нас возникает масса зависимостей, и мы обязаны ими как-то руководить.Дюже Зачастую для решения задачи зависимостей применяется Singleton, по сути глобальная переменная, к которой все имеют доступ.
Как Зачастую вам доводилось видеть сходственный код?

[[RequestManager sharedInstance] loadResourcesAtPath:@"http://example.com/resources" withDelegate:self];
// либо
[[DatabaseManager sharedManager] saveResource:resource];

Данный подход применяется во множестве планов, но он имеет некоторые недочеты:

  • трудно застабать синглтон тот, что применяется внутри тестируемого класса
  • синглтон, по сути, глобальная переменная
  • с точки зрения SRP объект не должен контролировать свое Singleton’овское поведение

Первую задачу решить достаточно легко — необходимо применять свойства:

@interface ViewController : UIViewController

@property (nonatomic, strong) RequestManager *requestManager;

@end

Но данный подход имеет другие минусы — сейчас кто-то должен «заполнить» это качество.
Магия Крови содействует решению этой задачи.

Внедрение зависимостей

Эти задачи не являются уникальными для Objective-C. Если мы посмотрим на больше «индустриальные» языки, такие как Java либо C , то сумеем обнаружить решение. Обширно применяемый подход в Java — Внедрение зависимостей (Dependency Injection, DI)

DI разрешает применять requestManager в виде синглтона в приложении, но в тестах заменяя его на mock. При этом ни RequestManager ни ViewController не знает ничего о синглтонах, потому что это поведение контролирует DI фрэймворк.

На гитхабе лежит много реализаций DI на Objective-C, но они имеют свои минусы:

  • изложение зависимостей с применением макросов либо строковых констант
  • внедрение происходит только если объект сделан «специальным» образом (данный вариант не будет трудиться с ViewController‘ами и View сделанными из сторибордов и нибов)
  • внедряемый класс должен реализовать некоторый протокол (не будет трудиться со сторонними либо стандартными библиотеками)
  • инициализацию немыслимо перенести в обособленный модуль
  • XML

Магия Крови

Давайте посмотрим на следующий фрэймворк (с другими недостатками) — Магия Крови (BloodMagic, BM)

BM реализует подобие кастомных аттрибутов для свойств Objective-C классов. Он проектировался с учетом расширяемости и в скором времени будет добавлено огромнее фич. На данный момент реализован только один аттрибут — Lazy, Ленивая Инициализация.

Данный признак разрешает инициализировать свойства по требованию, без написания рутинного кода. Таким образом взамен сходственных простыней:

@­interface ViewController : UIViewController

@­property (nonatomic, strong) ProgressViewService *progressViewService;
@­property (nonatomic, strong) ResourceLoader *resourceLoader;

@­end

@­implementation ViewController

- (void)loadResources
{
    [self.progressViewService showProgressInView:self.view];

    self.resourceLoader.delegate = self;
    [self.resourceLoader loadResources];
}

- (ProgressViewService *)progressViewService
{
    if (_progressViewService == nil) {
        _progressViewService = [ProgressViewService new];
    }

    return _progressViewService;
}

- (ResourceLoader *)resourceLoader
{
    if (_resourceLoader == nil) {
        _resourceLoader = [ResourceLoader new];
    }

    return _resourceLoader;
}

@­end

дозволено легко написать:

@­interface ViewController : UIViewController
    <BMLazy>

@­property (nonatomic, strong) ProgressViewService *progressViewService;
@­property (nonatomic, strong) ResourceLoader *resourceLoader;

@­end

@­implementation ViewController

@­dynamic progressViewService;
@­dynamic resourceLoader;

- (void)loadResources
{
    [self.progressViewService showProgressInView:self.view];

    self.resourceLoader.delegate = self;
    [self.resourceLoader loadResources];
}

@­end

И все. Оба @­dynamic свойства будут сделаны при первом вызове self.progressViewService иself.resourceLoader. Эти объекты будут освобождены как и обычные свойства — позже освобожденияViewController‘а.

Магия Крови и Внедрение Зависимостей

По умолчанию для создания объектов применяется способ класса new. Но есть вероятность описать и свои, кастомные инициализаторы, которые являются ключевой спецификой BM в качестве DI фрэймворка.

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

BMInitializer *initializer = [BMInitializer lazyInitializer];
initializer.propertyClass = [ProgressViewService class];
initializer.initializer = ^id (id sender){
  return [[ProgressViewService alloc] initWithViewController:sender];
};
[initializer registerInitializer];

propertyClass — инициализатор регистрируется для свойств этого класса.
initializer — блок, тот, что будет вызван для инициализации объекта. Если данный блок nil либо инициализатор не обнаружен, то объект будет сделан при помощи способа new.
sender — экземпляр класса контейнера.

Также инициализатор имеет качество containerClass, которое разрешает описать создание одного и того же свойства по различному, базируясь на контейнере. К примеру:

BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer];
usersLoaderInitializer.propertyClass = [ResourceLoader class];
usersLoaderInitializer.containerClass = [UsersViewController class];
usersLoaderInitializer.initializer = ^id (id sender){
  return [ResourceLoader usersLoader];
};
[usersLoaderInitializer registerInitializer];

BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer];
projectsLoaderInitializer.propertyClass = [ResourceLoader class];
projectsLoaderInitializer.containerClass = [ProjectsViewController class];
projectsLoaderInitializer.initializer = ^id (id sender){
  return [ResourceLoader projectsLoader];
};
[projectsLoaderInitializer registerInitializer];

Таким образом, для UsersViewController и ProjectsViewController будут сделаны различные объекты. По умолчанию containerClass равен классу NSObject.

Инициализаторы помогают избавиться от разных shared* способов и хардкода, описанного в начале статьи:

BMInitializer *initializer = [BMInitializer lazyInitializer];
initializer.propertyClass = [RequestManager class];
initializer.initializer = ^id (id sender){

  static id singleInstance = nil;
  static dispatch_once_t once;
  dispatch_once(&once, ^{
    singleInstance = [RequestManager new];
  });
  return singleInstance;

};
[initializer registerInitializer];

Организация и хранение инициализаторов

В плане может быть уйма инициализаторов, потому имеет толк перенести их в отдельное место/модуль.

Недурным решением является разнесение их по различным файлам и применение флагов компилятора. В Магии Крови есть примитивный макрос, тот, что прячет эти признаки — lazy_initializer. Все что необходимо, это сделать файл без заголовка и добавить его в фазу компиляции.

Пример:

//  LoaderInitializer.m

#import <BloodMagic/Lazy.h>

#import "ResourceLoader.h"
#import "UsersViewController.h"
#import "ProjectsViewController.h"

lazy_initializer ResourseLoaderInitializers()
{
    BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer];
    usersLoaderInitializer.propertyClass = [ResourceLoader class];
    usersLoaderInitializer.containerClass = [UsersViewController class];
    usersLoaderInitializer.initializer = ^id (id sender){
        return [ResourceLoader usersLoader];
    };
    [usersLoaderInitializer registerInitializer];

    BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer];
    projectsLoaderInitializer.propertyClass = [ResourceLoader class];
    projectsLoaderInitializer.containerClass = [ProjectsViewController class];
    projectsLoaderInitializer.initializer = ^id (id sender){
        return [ResourceLoader projectsLoader];
    };
    [projectsLoaderInitializer registerInitializer];
}

lazy_initializer будет заменен на __attribute__((constructor)) static void. Признак constructorобозначает что данный способ будет вызван прежде чем main (тут есть больше детальное изложение: GCC. Function Attributes).

Планы на ближайшее грядущее

  • реализовать поддержку протоколов (@­property (nonatomic, strong) id<ResourceLoader> loader)
  • добавить изложение работы и реализации
  • описать добавление новых признаков
  • добавить огромнее признаков

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB