Общая проблема проектирования может быть описана следующим образом:
У меня есть соединение с веб-сокетом, для которого требуется соблюдение строгого жизненного цикла - оно хочет, чтобы connect
и disconnect
вызывались надлежащим образом, и, поскольку оно говоритв систему, он использует.В этом соединении с веб-сокетом у нас есть несколько различных объектов подписки, каждый из которых имеет строгий жизненный цикл, который он хочет соблюдать (subscribe
и unsubscribe
), и для успешной работы этих операций зависит состояние его родительского веб-сокета.
Вот график идеального поведения для трех вложенных наблюдаемых жизненного цикла, где C зависит от B, который зависит от A:
A = someInput.switchMap((i) => LifecycleObservable())
B = A.switchMap((a) => LifecycleObservable())
C = B.switchMap((b) => LifecycleObservable())
C.listen(print);
// <-- listen to c
// <-- produce [someInput]
setup A
setup B
setup C
// <-- c is produced
// <-- c is unsubscribed
teardown C
teardown B
teardown A
// <-- C is re-subscribed-to
setup A
setup B
setup C
// <-- produce [someInput]
teardown C
teardown B
teardown A
setup A
setup B
setup C
// <-- c is produced
Первый вопрос: Это анти-паттерн?Я не смог найти много об этом шаблоне в сети, но это похоже на довольно стандартную вещь, с которой вы столкнулись бы с наблюдаемыми: некоторые объекты просто имеют жизненный цикл, а некоторые объекты могут захотеть зависеть от этого.
Я могу довольно близко приблизиться к этому идеальному поведению, используя что-то вроде этого:
class LifecycleObservable {
static Observable<T> fromObservable<T>({
@required Observable<T> input,
@required Future<void> Function(T) setup,
@required Future<void> Function(T) teardown,
}) {
return input.asyncMap((T _input) async {
await setup(_input);
return _input;
}).switchMap((T _input) {
return Observable<T>(Observable.never()) //
.startWith(_input)
.doOnCancel(() async {
await teardown(_input);
});
});
}
}
Этот код принимает поток объектов с состоянием, запускает на них setup
по мере их создания иteardown
для них как суб-наблюдаемого в switchMap
отменяется.
Проблема возникает, когда в исходной идеализированной временной шкале создается вторая [someInput]
: используя код выше, я получаюcallgraph, такой как
// <-- listen to c
// <-- produce [someInput]
setup A
setup B
setup C
// <-- c is produced
// <-- produce [someInput]
teardown A
setup A
teardown B
setup B
teardown C
setup C
// <-- c is produced
, проблема в том, что если B зависит от A (как, например, вызов unsubscribe
из подписки, которая зависит от открытого транспорта веб-сокетов), этот порядок разрыва нарушает ожидаемый жизненный цикл каждого объекта (подписка пытается отправить unsubscribe
по закрытому транспорту веб-сокета.