Как условно буферировать значения RACSignal?
Я работаю над кодом, который взаимодействует с удаленным API через websockets. Мой уровень данных отвечает за установление и мониторинг подключения к сети. Он также содержит методы, которые могут быть использованы приложением для отправки сообщений websocket для отправки. Код приложения не должен отвечать за проверку состояния подключения к Интернету, а также пожара и забыть.
В идеале я хотел бы, чтобы слой данных функционировал следующим образом:
- Когда уровень данных не подключен к конечной точке websocket (
self.isConnected == NO
), сообщения буферизуются внутри.
- Когда соединение становится доступным (
self.isConnected == YES
), буферизованные сообщения немедленно отправляются, и любые последующие сообщения отправляются немедленно.
Вот что я смог придумать:
#import "RACSignal+Buffering.h"
@implementation RACSignal (Buffering)
- (RACSignal*)bufferWithSignal:(RACSignal*)shouldBuffer
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
NSMutableArray* bufferedValues = [[NSMutableArray alloc] init];
__block BOOL buffering = NO;
void (^bufferHandler)() = ^{
if (!buffering)
{
for (id val in bufferedValues)
{
[subscriber sendNext:val];
}
[bufferedValues removeAllObjects];
}
};
RACDisposable* bufferDisposable = [shouldBuffer subscribeNext:^(NSNumber* shouldBuffer) {
buffering = shouldBuffer.boolValue;
bufferHandler();
}];
if (bufferDisposable)
{
[disposable addDisposable:bufferDisposable];
}
RACDisposable* valueDisposable = [self subscribeNext:^(id x) {
[bufferedValues addObject:x];
bufferHandler();
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
if (valueDisposable)
{
[disposable addDisposable:valueDisposable];
}
return disposable;
}];
}
@end
Наконец, это псевдокод для того, как он будет использоваться:
@interface APIManager ()
@property (nonatomic) RACSubject* requests;
@end
@implementation WebsocketDataLayer
- (id)init
{
self = [super init];
if (self) {
RACSignal* connectedSignal = RACObserve(self, connected);
self.requests = [[RACSubject alloc] init];
RACSignal* bufferedApiRequests = [self.requests bufferWithSignal:connectedSignal];
[self rac_liftSelector:@selector(sendRequest:) withSignalsFromArray:@[bufferedApiRequests]];
}
return self;
}
- (void)enqueueRequest:(NSString*)request
{
[self.requests sendNext:request];
}
- (void)sendRequest:(NSString*)request
{
DebugLog(@"Making websocket request: %@", request);
}
@end
Мой вопрос: это правильный подход для значений буферизации? Есть ли более идиоматический RAC способ справиться с этим?
Ответы
Ответ 1
Буферизация может рассматриваться как нечто, применимое к отдельным запросам, что приводит к естественной реализации с использованием -flattenMap:
и RACObserve
:
@weakify(self);
RACSignal *bufferedRequests = [self.requests flattenMap:^(NSString *request) {
@strongify(self);
// Waits for self.connected to be YES, or checks that it already is,
// then forwards the request.
return [[[[RACObserve(self, connected)
ignore:@NO]
take:1]
// Replace the property value with our request.
mapReplace:request];
}];
Если упорядочение важно, вы можете заменить -flattenMap:
на -map:
plus -concat
. Эти реализации избегают необходимости в каких-либо пользовательских операторах и работают без ручных подписок (которые, как известно, беспорядочны).
Ответ 2
Вы почти точно так же, как и в bufferWithTime:
, и я не могу думать о каких-либо существующих операциях, которые реализовать его более идиоматично. (Вероятно, именно по этой причине bufferWithTime
был реализован таким образом.) Анализ вашего кода с использованием этой реализации может выявить некоторые ошибки, о которых вы не думали.
Но, честно говоря, это не должно быть так сложно. Должна существовать операция буферизации, которая буферизует вывод и выводит содержимое, когда срабатывает сигнал запуска. Вероятно, большая часть буферизации может быть реализована с точки зрения этой функциональности, поэтому, если бы она добавила ценность в фреймворк.