Reactivecocoa: Лучший шаблон проектирования для запуска только одной асинхронной операции и совместного использования результата сигнала

Созданный на 27 сент. 2013  ·  11Комментарии  ·  Источник: ReactiveCocoa/ReactiveCocoa

Давайте представим, что RACSignal * updateSignal инкапсулирует некоторую асинхронную задачу. Когда происходит подписка, я хочу, чтобы каждая новая подписка во время асинхронной задачи получала тот же результат, что и первый подписчик.
Прямо сейчас мой код выглядит так:

- (RACSignal *)doWork
{
    if (_sharedWorkSignal) return _sharedWorkSignal;

    RACSignal *requestSignal = ...;
    _sharedWorkSignal = [[[[requestSignal publish] autoconnect] doCompleted:^{
        _sharedWorkSignal = nil;
    }] doError:^(NSError *error) {
        _sharedWorkSignal = nil;
    }];

    return _sharedWorkSignal;
}

Это работает, но мне не очень нравится, как это выглядит. Я упускаю здесь что-то важное?

question

Все 11 Комментарий

Для этого вы можете использовать -replay или -replayLazily .

Но все же мне пришлось использовать эту переменную instnace для хранения воспроизводимого сигнала, пока выполняется асинхронная работа?

Я не совсем уверен, что понимаю ваш вопрос. Вы можете проверить мой пример получения токена аутентификации перед вызовами API , поскольку он использует аналогичный шаблон.

Возможно, я дал неверное описание. Пока асинхронная операция выполняется из-за первой подписки, я хочу, чтобы любая другая подписка разделяла результат, но когда эта работа будет выполнена и сигнал завершится, я хочу, чтобы любая новая подписка снова начала эту работу. Извините за мой английский ;)

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

  1. Чтение и запись _sharedWorkSignal не являются потокобезопасными. Для этого вы должны использовать свойство atomic .
  2. Вероятно, вы захотите использовать -replayLazily вместо -publish и -autoconnect , иначе новые подписчики не получат значения, отправленные до того, как они присоединятся.
  3. Вы можете использовать -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 без запуска КАЖДОГО сетевого запроса, но при необходимости мне нужно очистить его, чтобы следующая подписка фактически инициировала сетевой запрос.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги