Как я могу подписаться на завершение сигналов выполнения команды без вложенной подписки?

Я безуспешно пробовал следующее. Эквивалент с использованием -subscribeNext: работает, как и ожидалось.

// A
[[_viewModel.loginCommand.executionSignals flatten] subscribeCompleted:^{
    NSLog(@"A");
}];

Моя единственная рабочая реализация выглядит следующим образом:

// B
[_viewModel.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
    [loginSignal subscribeCompleted:^{
        NSLog(@"B");
    }];
}];

Почему -flatten не работает в "A" и как мне переписать "B", чтобы не использовать вложенную подписку?


person Paul Young    schedule 13.03.2014    source источник


Ответы (2)


Оператор -flatten возвращает сигнал, который завершается только после завершения всех внутренних сигналов, что также требует завершения внешнего сигнала. То же самое относится и к -concat. Из-за этого, как только вы применяете любой оператор, результирующий сигнал не имеет представления об индивидуальном завершении, а только о конечном совокупном завершении.

В качестве альтернативы вложенным подпискам вы можете преобразовать внутренние сигналы так, чтобы они отправляли значение, означающее завершение. Один из способов сделать это с помощью -materialize:

[[[_viewModel.loginCommand.executionSignals
    map:^(RACSignal *loginSignal) {
        // Using -ignoreValues ensures only the completion event is sent.
        return [[loginSignal ignoreValues] materialize];
    }]
    concat]
    subscribeNext:^(RACEvent *event) {
        NSLog(@"Completed: %@", event);
    }];

Обратите внимание, что я использовал -concat вместо -flatten, так как это соответствует семантике последовательного выполнения RACCommand по умолчанию. В конечном итоге они делают то же самое в этом случае, -flatten вырождается в поведение -concat, потому что команда выполняет только сигналы по одному за раз.

Использование -materialize — не единственный способ сделать это, просто происходит отправка значения, представляющего завершение, но это может быть любое значение, которое вы считаете подходящим для вашего варианта использования.

person Dave Lee    schedule 13.03.2014
comment
Это довольно распространенная операция - показать подтверждение успеха операции. Например, я хочу опубликовать сообщение и ожидаю, что операция завершится или завершится неудачно. Неужели нет способа сделать это более четко? Я имею в виду, что этот подход по-прежнему является просто подпиской на вложенный сигнал выполнения. Что, например, если команда имеет более одного сигнала выполнения? - person Slabko; 13.02.2015
comment
executionSignals RACCommand не предоставляет события завершения напрямую. Если вы не хотите использовать materialize, другой вариант — использовать осмысленное значение, которое задокументировано как означающее успешное завершение. В случае сигнала, который не отправляет никаких значений, вы можете отправить любое значение, чтобы указать завершение, и просто проигнорировать его. Да, еще один вариант — использовать оператор doCompleted: в качестве альтернативы вложенной подписке. - person Dave Lee; 14.02.2015
comment
Таким образом, в основном RACCommand не предназначен для отслеживания неудачной или успешной операции, он предназначен только для отслеживания того, когда операция выполняется, а когда нет. Я правильно понимаю? - person Slabko; 15.02.2015
comment
Не совсем. Вы можете отслеживать ошибки, вы можете отслеживать значения/результаты и даже отслеживать завершение, но, к сожалению, только косвенно/неидиоматически. Его типичная/основная цель состоит в том, чтобы предотвратить одновременные действия какого-либо типа, а не позволять чему-то происходить по одному за раз. Чтобы сделать это более мощным, он предоставляет сигнал, который указывает, может ли действие начаться. Все это полезно для пользовательского интерфейса. - person Dave Lee; 15.02.2015

Я просто подумал, что технически успешное завершение - это просто изменение состояния выполнения на НЕТ после того, как -executionSingals отправил значение хотя бы один раз и не было ошибки после того, как состояние выполнения изменилось на ДА в последний раз.

На основе таких мыслей я сделал категорию:

#import "RACCommand+ARLCompletedSignal.h"

@implementation RACCommand (ARLCompletedSignal)

- (RACSignal *)completed
{
    RACSignal *executing = self.executing;
    RACSignal *signals = self.executionSignals;
    RACSignal *errors = self.errors;

    RACSignal *startingExecution = [RACSignal combineLatest:@[executing, [signals take:1]]
                                                     reduce:^id(NSNumber *executing, id _){ return executing; }];

    return [[startingExecution
       ignore:@NO]
       flattenMap:^RACStream *(id value) {
           RACSignal *comletedOrFailed = [[executing ignore:@YES] subscribeOn:[RACScheduler scheduler]];
           return [[[comletedOrFailed take:1] takeUntil:errors] map:^id(id value) { return nil; }];
       }];
}

@end

Заголовок:

@interface RACCommand (ARLCompletedSignal)

@property (nonatomic, readonly) RACSignal *completed;

@end

Здесь -comleted отправляет nil, когда команда успешно завершает свою работу. Также на https://gist.github.com/slabko/546de430a16994a5da8e вы можете найти версию, который отправляет YES, если операция завершается успешно, или NO, если нет.

Я попробовал это в некоторых довольно простых случаях, и это сработало. Пожалуйста, дайте мне знать, если это не работает для вас.

Однако я считаю, что в большинстве случаев подписка на завершение исходного сигнала до того, как вы передадите его команде, является лучшим, «бесхитростным» вариантом.

person Slabko    schedule 14.02.2015