Цепочка зависимых сигналов в ReactiveCocoa

В ReactiveCocoa, если мы объединяем несколько зависимых сигналов, мы должны использовать subscribeNext: для следующего сигнала в цепочке, чтобы получить значение, созданное предыдущим сигналом (например, результат асинхронной операции). Итак, через некоторое время код превращается в примерно такой (лишние детали опущены):

RACSignal *buttonClickSignal = [self.logIn rac_signalForControlEvents:UIControlEventTouchUpInside];

[buttonClickSignal subscribeNext:^(UIButton *sender) {    // signal from a button click
    // prepare data

    RACSignal *loginSignal = [self logInWithUsername:username password:password];    // signal from the async network operation

    [loginSignal subscribeNext:^void (NSDictionary *json) {
        // do stuff with data received from the first network interaction, prepare some new data

        RACSignal *playlistFetchSignal = [self fetchPlaylistForToken:token];         // another signal from the async network operation

        [playlistFetchSignal subscribeNext:^(NSDictionary *json) {
            // do more stuff with the returned data
        }];

        // etc
    }];
}];

Эта постоянно увеличивающаяся вложенность выглядит не намного лучше того нереактивного примера, который приведен в документации:

[client logInWithSuccess:^{
    [client loadCachedMessagesWithSuccess:^(NSArray *messages) {
        [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) {
            NSLog(@"Fetched all messages.");
        } failure:^(NSError *error) {
            [self presentError:error];
        }];
    } failure:^(NSError *error) {
        [self presentError:error];
    }];
} failure:^(NSError *error) {
    [self presentError:error];
}];

Я что-то пропустил? Есть ли лучший шаблон цепочки зависимой работы в ReactiveCocoa?


person Sergey Mikhanov    schedule 03.04.2013    source источник


Ответы (1)


Это когда RACStream и RACSignal становятся действительно полезными. В вашем конкретном примере вы можете использовать -flattenMap: для включения результатов в новые сигналы:

[[[buttonClickSignal
    flattenMap:^(UIButton *sender) {
        // prepare 'username' and 'password'
        return [self logInWithUsername:username password:password];
    }]
    flattenMap:^(NSDictionary *json) {
        // prepare 'token'
        return [self fetchPlaylistForToken:token];
    }]
    subscribeNext:^(NSDictionary *json) {
        // do stuff with the returned playlist data
    }];

Если вам не нужны результаты какого-либо шага, вы можете вместо этого использовать -sequenceMany: или -sequenceNext: для аналогичного эффекта (но для более четкого выражения намерения).

person Justin Spahr-Summers    schedule 05.04.2013
comment
Это решение работает для меня. Я написал статья в блоге, в которой этот подход объясняется более подробно. - person Richard H Fung; 05.08.2013
comment
@ Джастин, не могли бы вы поподробнее рассказать о том, как правильно обрабатывать ошибки? - person DogpatchTech; 01.03.2014
comment
@DogpatchTech Для этого вы можете использовать -catch: или -catchTo:. См. это объяснение. - person Justin Spahr-Summers; 03.03.2014
comment
-sequenceMany: и -sequenceNext: устарели в 2.0+; -sequenceMany: предполагается заменить на -flattenMap:, игнорируя аргументы блока; -sequenceNext: заменяется на -then:. (См. запись журнала изменений здесь: github.com /ReactiveCocoa/ReactiveCocoa/blob/master/) - person febeling; 15.05.2014
comment
@JustinSpahr-Summers, как бы вы переписали приведенный выше код, если бы fetchPlaylistForToken нужно было вызывать после loginWithUsername completed вместо отправки события next? - person Valerio Santinelli; 12.03.2015
comment
@ValerioSantinelli Оператор типа -then: или -materialize или что-то в этом роде. Хотя это звучит как запах кода. - person Justin Spahr-Summers; 12.03.2015
comment
@JustinSpahr-Саммерс, ты можешь использовать concat? - person onmyway133; 31.03.2015