Рефакторинг в ReactiveCocoa

Так что я только недавно начал работу с ReactiveCocoa, и я решил, что лучший способ учиться – это просто перепрыгнуть прямо и начать рефакторинг существующего кода, который у меня есть. Я хотел получить критику и убедиться, что я направляюсь в правильном направлении.

Поэтому в приложении я рефакторинг, у меня есть тонна кода, которая выглядит следующим образом:

[self.ff getArrayFromUri:@"/States?sort=name asc" onComplete:^(NSError *theErr, id theObj, NSHTTPURLResponse *theResponse) { if(!theErr) { //do something with theObj } else { //handle the error } }]; 

В настоящее время у меня есть этот рефакторинг в ReactiveCocoa:

 -(void)viewDidLoad { //ReactiveCocoa RACCommand *command = [RACCommand command]; RACSubject *subject = [RACSubject subject]; [[[command addSignalBlock:^RACSignal *(id value) { NSError *err; NSArray *array = [self.ff getArrayFromUri:@"/States" error:&err]; err ? [subject sendError:err] : [subject sendNext:array]; return [RACSignal empty]; }]switchToLatest]deliverOn:[RACScheduler mainThreadScheduler]]; [subject subscribeNext:^(NSArray *x) { [self performSegueWithIdentifier:kSomeSegue sender:x]; } error:^(NSError *error) { NSLog(@"the error = %@", error.localizedDescription); }]; self.doNotLocation = [UIButton buttonWithType:UIButtonTypeCustom]; [self.doNotLocation setBackgroundImage:[UIImage imageNamed:@"BlackButton_small.png"] forState:UIControlStateNormal]; [[self.doNotLocation rac_signalForControlEvents:UIControlEventTouchUpInside] executeCommand:command]; RAC(self.doNotLocation.enabled) = RACAbleWithStart(command, canExecute); RAC([UIApplication sharedApplication],networkActivityIndicatorVisible) = RACAbleWithStart(command, executing); } 

Это о том, как я должен это делать, используя RACSubject, или есть лучший способ? Эта концепция для меня совершенно новая, так как моими единственными языками программирования были Java и Objective-C, поэтому этот функциональный реактивный образ мышления меня немного отбросил.

    2 Solutions collect form web for “Рефакторинг в ReactiveCocoa”

    К сожалению, есть пара проблем с образцом кода, который вы представили:

    1. Блок, переданный в -addSignalBlock: возвращает пустой сигнал. Это должен быть предупреждающий флаг, поскольку почти нет значений возврата нежелательной почты. В этом случае это означает, что блок выполняет свою работу синхронно. Чтобы избежать блокировки основного потока, вы должны создать сигнал, который работает асинхронно, и вернуть его.
    2. -switchToLatest и -deliverOn: ничего не делают. Большинство сигнальных операторов работают только тогда, когда речевой сигнал подписан. В этом случае он просто исчезает в эфире.

    Мы можем решить обе эти проблемы сразу. -addSignalBlock: возвращает сигнал сигналов, возвращаемых в блоке . Если мы вернем что-то значимое, его можно обработать вне этого метода.

    Прежде всего, это нужно добавить в начало:

     @weakify(self); 

    Когда @strongify(self) используется ниже, это предотвратит цикл сохранения . Это необходимо, потому что RACCommand живет до тех пор, как self .

    Теперь создание внутренних сигналов:

     RACSignal *requestSignals = [command addSignalBlock:^(id value) { return [RACSignal start:^(BOOL *success, NSError **err) { @strongify(self); NSArray *array = [self.ff getArrayFromUri:@"/States" error:err]; *success = (array != nil); return array; }]; }]; 

    Внутри блока это просто создает сигнал, который вызывается -getArrayFromUri:error: и возвращает его результаты или ошибку, если это произошло. +start: будет гарантировать, что работа будет выполняться в фоновом режиме.

    Из всего этого мы получаем requestSignals , который является сигналом этих созданных сигналов . Это может полностью заменить RACSubject :

     RACSignal *arrays = [[[requestSignals map:^(RACSignal *request) { return [request catch:^(NSError *error) { NSLog(@"the error = %@", error); return [RACSignal empty]; }]; }] flatten] deliverOn:RACScheduler.mainThreadScheduler]; 

    Сначала мы преобразуем каждый внутренний сигнал в журнал, затем игнорируем ошибки. (Это немного сложно, но оператор RAC может быть добавлен, чтобы сделать это в будущем.)

    Затем мы сглаживаем сигнал сигналов. Результат, arrays , является сигналом, который проходит через значения всех внутренних сигналов. Вот почему нам пришлось игнорировать ошибки – если кто-то из них достиг этого момента, мы перестали бы получать все значения от внутренних сигналов навсегда.

    Наконец, мы «поднимаем» селектор для вызова:

     [self rac_liftSelector:@selector(performSegueWithIdentifier:sender:) withObjects:kSomeSegue, arrays]; 

    Это будет resend -performSegueWithIdentifier:sender: всякий раз, когда arrays отправляют новое значение (которое будет NSArray возвращено из сети). Вы можете думать об этом как о вызове метода с течением времени . Это лучше, чем подписка, потому что это упрощает побочные эффекты и управление памятью.

    По моему опыту с каркасом, я обнаружил, что очень RACSubject приходится использовать RACSubject , особенно для одноразовых сигналов, подобных этому. RACSubjects представляют собой изменяемые сигналы, которые вам не нужны в этом случае, и могут фактически увеличить сложность вашего кода. Было бы намного лучше вернуть ванильный сигнал (через +[RACSignal createSignal:] ) внутри этого командного блока, а затем получить код запроса, составляющий тело сигнала:

     [[[command addSignalBlock:^RACSignal *(id value) { // return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //Request code here return nil; }]; }]switchToLatest]deliverOn:[RACScheduler mainThreadScheduler]]; 

    Или, что еще лучше, вы можете реорганизовать getArrayFromUri:error: вернуть сигнал и избавиться от этого трехмерного утверждения:

      [[[command addSignalBlock:^RACSignal *(id value) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //... [[self getArrayFromUri:@"/States"]subscribeError:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [subscriber sendNext:array]; }]; return nil; }]; }]switchToLatest]deliverOn:RACScheduler.mainThreadScheduler]; 

    Что касается проблемы подписки на следующей строке, это можно рассматривать как побочные эффекты сигнала, поэтому мы можем сделать это явно с соответствующими вариантами do: применяется к сигналу для команды:

      [[[command addSignalBlock:^RACSignal *(id value) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [[[[self getArrayFromUri:@"/States"]doError:^(NSError *error) { NSLog(@"the error = %@", error.localizedDescription); [subscriber sendError:err]; }] doNext:^(NSArray *array) { [subscriber sendNext:array]; [self performSegueWithIdentifier:kSomeSegue sender:array]; }] subscribeCompleted:^{ [subscriber sendCompleted]; }]; return [RACDisposable disposableWithBlock:^{ // Cleanup }]; }]; }]switchToLatest]deliverOn:[RACScheduler mainThreadScheduler]]; 

    Наконец, поскольку команды работают иначе, чем сигналы, внешние операторы не будут оцениваться (спасибо, @jspahrsummers), поэтому вы можете их удалить.

     [command addSignalBlock:^RACSignal *(id value) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [[[[self getArrayFromUri:@"/States"]doError:^(NSError *error) { NSLog(@"the error = %@", error.localizedDescription); [subscriber sendError:err]; }] doNext:^(NSArray *array) { [subscriber sendNext:array]; [self performSegueWithIdentifier:kSomeSegue sender:array]; }] subscribeCompleted:^{ [subscriber sendCompleted]; }]; return [RACDisposable disposableWithBlock:^{ // Cleanup }]; }]; }]; 
    PhoneC: Разработка iOS проста с помощью XCode, Swift3, UITableView, cocatouch, давайте создадим приложения для iPhone, iPad и Macbook.