Давайте представим, что RACSignal * updateSignal инкапсулирует некоторую асинхронную задачу. Когда происходит подписка, я хочу, чтобы каждая новая подписка во время асинхронной задачи получала тот же результат, что и первый подписчик.
Прямо сейчас мой код выглядит так:
- (RACSignal *)doWork
{
if (_sharedWorkSignal) return _sharedWorkSignal;
RACSignal *requestSignal = ...;
_sharedWorkSignal = [[[[requestSignal publish] autoconnect] doCompleted:^{
_sharedWorkSignal = nil;
}] doError:^(NSError *error) {
_sharedWorkSignal = nil;
}];
return _sharedWorkSignal;
}
Это работает, но мне не очень нравится, как это выглядит. Я упускаю здесь что-то важное?
Для этого вы можете использовать -replay
или -replayLazily
.
Но все же мне пришлось использовать эту переменную instnace для хранения воспроизводимого сигнала, пока выполняется асинхронная работа?
Я не совсем уверен, что понимаю ваш вопрос. Вы можете проверить мой пример получения токена аутентификации перед вызовами API , поскольку он использует аналогичный шаблон.
Возможно, я дал неверное описание. Пока асинхронная операция выполняется из-за первой подписки, я хочу, чтобы любая другая подписка разделяла результат, но когда эта работа будет выполнена и сигнал завершится, я хочу, чтобы любая новая подписка снова начала эту работу. Извините за мой английский ;)
В таком случае я не уверен, что есть подход значительно лучше, чем то, что вы сделали. Однако есть некоторые моменты, на которые следует обратить внимание:
_sharedWorkSignal
не являются потокобезопасными. Для этого вы должны использовать свойство atomic
.-replayLazily
вместо -publish
и -autoconnect
, иначе новые подписчики не получат значения, отправленные до того, как они присоединятся.-finally:
вместо -doCompleted:
и -doError:
.Большой! Большое спасибо, Джастин! Как я мог пропустить -finally
. И спасибо за ваши заметки! Закрыто.
: блестки:
Если вы не хотите использовать методы, переменные экземпляра или свойства, вы можете попробовать что-то вроде этого:
RACSignal *testSig = [[RACSignal return:[NSMutableArray arrayWithCapacity:1]] map:^id(NSMutableArray *array) {
if (array.count == 0) {
RACSignal *sig = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// Do stuff
[subscriber sendNext:something];
[subscriber sendCompleted];
return nil;
}] doCompleted:^{
[array removeAllObjects];
}];
[array addObject:sig.replayLast];
}
return array.lastObject;
}].replayLazily;
Используя массив или тему, вы можете сохранить ссылку на что-либо без объявления переменной или использования свойства.
Этот код не является потокобезопасным. Иногда использование переменной - действительно лучший ответ.
Хорошо ... похоже, что самая безопасная стратегия - использовать атомарные свойства в качестве «якорей», особенно для любых переменных, которые вы вплетаете и извлекаете из блоков RAC.
У меня такая же проблема с общим сигналом, который я не буду «повторно запускать», когда это необходимо.
Я пробовал много возможных решений в стиле RACMultiConnection, но независимо от того, с чем я всегда сталкиваюсь с одной и той же проблемой: каждый раз, когда я хочу повторно запустить свой исходный сигнал, он отправляет следующий: всем предыдущим подписчикам.
Подробнее:
1) сетевой запрос выполняется с помощью удобных методов, которые завершают старую реализацию делегата:
RACSignal *performRequestSignal =
[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// Create data received signal
RACSignal *dataReceivedSignal = [[self rac_signalForSelector:@selector(dataReceived:forRequest:) fromProtocol:@protocol(MainDataControllerDelegate)] filter:^BOOL(RACTuple *returnTuple) {
DataRequest *request = returnTuple.second;
DataResponse *dataResponse = returnTuple.first;
return ((request.type == dataRequest.type) && ([dataResponse.value isKindOfClass:dataResponseClass]));
}];
// Subscribe
[dataReceivedSignal subscribeNext:^(RACTuple *returnTuple) {
DataResponse *dataResponse = returnTuple.first;
[subscriber sendNext:dataResponse.value];
[subscriber sendCompleted];
}];
// Create data request failed signal
RACSignal *requestFailedSignal = [self rac_signalForSelector:@selector(requestFailed:error:) fromProtocol:@protocol(MainDataControllerDelegate)];
// Subscribe
[requestFailedSignal subscribeNext:^(RACTuple *returnTuple) {
NSError *error = returnTuple.second;
[subscriber sendError:error];
}];
// Trigger the request to API
[[MainDataController sharedInstance] addRequest:dataRequest delegate:self priority:queuePriority];
return [RACDisposable disposableWithBlock:^{
[[MainDataController sharedInstance] cancelRequest:dataRequest];
}];
}];
return performRequestSignal;
2) Этот метод использует предыдущий для создания вспомогательного сигнала, который мы называем "retrieveThingsSignal".
- (RACSignal *)retrieveThings
{
if (_retrieveDebtors) return _retrieveDebtors;
RACSignal *requestSignal = [self retrieveThingsSignal];
_retrieveDebtors = [[requestSignal replayLast] finally:^{
_retrieveThings = nil;
}];
return _retrieveThings;
3) каждый раз, когда я не буду повторно запускать сигнал, запустите этот код
[[self retrieveThings] subscribeNext:^(NSArray *things) {
@strongify(self);
self.things = things;
} error:^(NSError *error) {
@strongify(self)
self.things = nil;
}];
Мой self.things
обновляется каждый раз, когда мне нужно сделать новый запрос к retreiveThings
но, установив точку останова внутри шага 1), я получаю, что next:
отправляется ВСЕМ предыдущим подписчикам. Если я дважды вызываю retrieveThings
, я получаю от этой точки останова, что базовый сигнал отправляет next:
двум разным подписчикам (один фактически запускает блок subscribeNext:
) . Если три-три подписчика и так далее ...
Я могу предотвратить такое поведение?
Я хотел бы поделиться последним отправленным значением из моего метода retrieveThings
без запуска КАЖДОГО сетевого запроса, но при необходимости мне нужно очистить его, чтобы следующая подписка фактически инициировала сетевой запрос.