2015-02-11 2 views
2

Представьте У меня есть два сигнала: дешевый и дорогой:ReactiveCocoa - Подписаться на второй сигнал, если первый завершается без значения

RACSignal *localSignal;  // Cheap signal. Sends object without network request 
          // if possible, otherwise completes immediately. 

RACSingal *networkSignal; // Expensive one. Always sends data, 
          // but requires expensive network operation. 

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

Следующее решение почти дает мне то, что я хочу, но оно всегда соответствует второму дорогому сигналу, даже если принимается значение от первого сигнала, а значение второго сигнала игнорируется.

[[localDataSignal concat:networkDataSignal] take:1]; 

Есть ли способ решить проблему эффективно?

+1

Я чувствую, что это не должно подписываться на дорогой сигнал и что это ошибка утилиты. Мог бы написать вопрос о проекте RAC. –

+0

Только что нашел проблему. См. Мой ответ ниже. Я толстый, так как локальный сигнал работает в том же потоке, на котором он подписан, возьмите: 1 даже не имеет шанса уничтожить всю последовательность. – Slabko

ответ

3

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

RACSignal *localSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 
    NSLog(@"Perfoming local operation"); 
    [subscriber sendNext:@"local value"]; 
    [subscriber sendCompleted]; 
    return nil; 
}]; 

Как я вижу проблемы (я не уверен на 100%) взять: 1 даже не было возможности распоряжаться каскадный сетевые сигналы. Но если я планирую локальный сигнал на отличном от планировщика основного потока, тогда возьмите: 1 работает как ожидалось - он разбивает цепочку после получения первого значения.

Простыми словами это фрагмент работы:

[[[localSignal deliverOn:[RACScheduler scheduler]] concat:networkSignal] take:1] 

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

Правило большого пальца - это проверить поток, на который работает сигнал, а затем принять: 1 должен выполнить свою работу, как ожидалось.

Update: -deliveOnMainThread также дает -take: возможность распоряжаться очереди.

+1

А я вижу. Исчерпывающий дизайн RAC 2 заключается в том, что события, отправленные синхронно во время подписки, всегда будут происходить до того, как любая цепочка может быть прорвана путем удаления. Таким образом, в этом случае это не сам поток, а то, что 'localSignal' отправляет события синхронно при подписке. Вам не нужно «deliverOn:» другого планировщика, вы даже можете доставить его на 'mainThreadScheduler'. Любой 'deliverOn:' создает скачок GCD, который добавляет асинхронность. Альтернативно, 'subscribeOn:' будет работать или даже 'delay: 0'. –

+0

Да, это была моя мысль, когда я пытался. Также вы правы, что также работает deliverOnMainThread. Спасибо, что указал на проблему: 1 проблема, поэтому я мог немного поиграть с ней. – Slabko

+0

На самом деле одна проблема, которую я вижу с этим подходом, заключается в том, что сигнал выполняет асинхронно, даже если он запланирован в основном потоке. В случае, если я использую его для операций пользовательского интерфейса, например, для UICollectionViewCell с изображением, теоретически, когда я применяю сигнал с изображением к ячейке, представление коллекции получает свою ячейку, а затем сигнал применяет к нему изображение. Если это происходит в разных кадрах синхронизации, я увижу мигание. В моем конкретном примере я не вижу этого, по-видимому, потому что я возвращаю ячейку, и сигнал применяет изображение в тех же кадрах синхронизации. – Slabko

1

Вы можете использовать:

- (RACSignal *)catchTo:(RACSignal *)signal; 

Как это:

[[localSignal catchTo:networkSignal] subscribeNext:^(id x) { 
    // If localSignal sends error, networkSignal will be subscribed. 
}]; 
+1

Спасибо за ответ. Проблема в том, что локальный сигнал не отправляет никаких ошибок. Он завершает или возвращает значение, а затем завершает.Поэтому мне нужно увидеть, если первый сигнал завершен, но если у меня нет никаких значений, я хочу быть подписан на второй сигнал (дорогой). – Slabko

+0

Мне кажется, что местный сигнал должен отправить сообщение об ошибке, если он не может отправить следующее значение, не так ли? –

1

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

[[RACSignal concat:@[ 
    localSignal, 
    [[RACSignal empty] deliverOn:RACScheduler.mainThreadScheduler], 
    networkSignal 
] take:1] 

Альтернатива deliverOn:, вы могли бы рассмотреть delay:, если это делает проще этот код, чтобы понять.

+0

Спасибо! Я обнаружил, что ошибаюсь, даже если я доставляю сигнал основному планировщику, он по-прежнему работает синхронно. Я просто смоделировал пример: https://db.tt/th1oXRWO. Также, если я доставляю сигнал другому планировщику, логически достаточно, он установит значение после возвращения ячейки: https://db.tt/N89bOO1W. На скриншотах вы можете увидеть синтетический тест, но я также пробовал его с помощью реальных UICollectionView и реальных ячеек - изображение устанавливается синхронно, прежде чем источник данных вернет ячейку. Я должен сказать, что вы благодарны за то, что вы указали правильное направление. – Slabko

+0

Просто попробовал - 'delay: 0' работает так же, как передача сигнала в разные потоки. – Slabko

Смежные вопросы