|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Observer basics |
| 4 | +groups: |
| 5 | + - {name: Home, url: ''} |
| 6 | + - {name: Tutorials , url: 'tutorials/'} |
| 7 | +--- |
| 8 | + |
| 9 | +- [Creating subject-bound observers](#creating-subject-bound-observers) |
| 10 | +- [Detaching observers manually](#detach-observers-manually) |
| 11 | +- [Using scoped observers](#using-scoped-observers) |
| 12 | +- [Observing temporary reactives](#observing-temporary-reactives) |
| 13 | + |
| 14 | +## Creating subject-bound observers |
| 15 | + |
| 16 | +As mentioned in the previous examples, by default observers are bound to the lifetime of the observed subject. |
| 17 | +Here's another demonstration of this behaviour: |
| 18 | +{% highlight C++ %} |
| 19 | +#include "react/Domain.h" |
| 20 | +#include "react/Event.h" |
| 21 | +#include "react/Observer.h" |
| 22 | + |
| 23 | +REACTIVE_DOMAIN(D, sequential) |
| 24 | +USING_REACTIVE_DOMAIN(D) |
| 25 | + |
| 26 | +void testFunc() |
| 27 | +{ |
| 28 | + auto trigger = MakeEventSource<D>(); |
| 29 | + |
| 30 | + Observe(trigger, [] { |
| 31 | + // ... |
| 32 | + }); |
| 33 | +} |
| 34 | +{% endhighlight %} |
| 35 | +After leaving `testFunc`, `trigger` is destroyed. |
| 36 | +This automatically detaches and destroys the observer as well. |
| 37 | +We don't have to worry about resource leaks or that the existence of an observer prevents subject from being destroyed. |
| 38 | + |
| 39 | + |
| 40 | +## Detaching observers manually |
| 41 | + |
| 42 | +Alternatively, observers can be detached explicitly before the lifetime of their subject ends. |
| 43 | +We can either do this externally, or from inside the observer function. |
| 44 | + |
| 45 | +The former is demonstrated here: |
| 46 | +{% highlight C++ %} |
| 47 | +void testFunc() |
| 48 | +{ |
| 49 | + auto trigger = MakeEventSource<D>(); |
| 50 | + |
| 51 | + ObserverT obs = Observe(trigger, [] (Token) { |
| 52 | + cout << "Triggered" << endl; |
| 53 | + }); |
| 54 | + |
| 55 | + trigger.Emit(); // output: Triggered |
| 56 | + |
| 57 | + obs.Detach(); // Remove the observer |
| 58 | + |
| 59 | + trigger.Emit(); // no output |
| 60 | +} |
| 61 | +{% endhighlight %} |
| 62 | + |
| 63 | +`ObserverT` is an alias for `Observer<D>` defined by `USING_REACTIVE_DOMAIN(D)`. |
| 64 | +The observer handle returned by `Observe` is stored and by calling its `Detach` member function, the underlying observer node is destroyed and the handle becomes invalid. |
| 65 | + |
| 66 | +While it exists and has not been invalidated, an observer handle also takes shared ownership of the observed subject, i.e. by saving it, we state our continued interest in subject. |
| 67 | +This behaviour comes in handy later. |
| 68 | + |
| 69 | +To detach an observer from the inside, the return value of the observer function is changed from `void` to `ObserverAction`: |
| 70 | +{% highlight C++ %} |
| 71 | +void testFunc() |
| 72 | +{ |
| 73 | + auto source = MakeEventSource<D,int>(); |
| 74 | + |
| 75 | + Observe(trigger, [] (int v) -> ObserverAction { |
| 76 | + if (v != 0) |
| 77 | + { |
| 78 | + cout << v << endl; |
| 79 | + return ObserverAction::next; |
| 80 | + } |
| 81 | + else |
| 82 | + { |
| 83 | + cout << "Detached" << endl; |
| 84 | + return ObserverAction::stop_and_detach; |
| 85 | + } |
| 86 | + }); |
| 87 | + |
| 88 | + source << 3 << 2 << 1 << 0; |
| 89 | + // output: 3 2 1 Detached |
| 90 | + |
| 91 | + source << 4; |
| 92 | + // no output |
| 93 | +} |
| 94 | +{% endhighlight %} |
| 95 | + |
| 96 | + |
| 97 | +## Using scoped observers |
| 98 | + |
| 99 | +Instead of calling `Detach` manually, we can utilize the common RAII idiom and transfer ownership of the observer handle to a scope guard: |
| 100 | + |
| 101 | +{% highlight C++ %} |
| 102 | +void testFunc() |
| 103 | +{ |
| 104 | + auto trigger = MakeEventSource<D>(); |
| 105 | + |
| 106 | + // Start inner scope |
| 107 | + { |
| 108 | + ScopedObserverT scopedObs |
| 109 | + ( |
| 110 | + Observe(trigger, [] (Token) { |
| 111 | + cout << "Triggered" << endl; |
| 112 | + }) |
| 113 | + ); |
| 114 | + |
| 115 | + trigger.Emit(); |
| 116 | + // output: Triggered |
| 117 | + } |
| 118 | + // End inner scope |
| 119 | + |
| 120 | + trigger.Emit(); |
| 121 | + // no output |
| 122 | +} |
| 123 | +{% endhighlight %} |
| 124 | + |
| 125 | +The observer is detached in the destructor of `ScopedObserverT`. |
| 126 | + |
| 127 | +## Observing temporary reactives |
| 128 | + |
| 129 | +One problem of tying the lifetime of the observer to its subject manifests when attempting to do the following: |
| 130 | +{% highlight C++ %} |
| 131 | +void testFunc() |
| 132 | +{ |
| 133 | + auto e1 = MakeEventSource<D>(); |
| 134 | + auto e2 = MakeEventSource<D>(); |
| 135 | + |
| 136 | + Observe(Merge(e1, e2), [] (Token) { |
| 137 | + cout << "Triggered!" << endl; |
| 138 | + }); |
| 139 | + |
| 140 | + e1.Emit(); // no output |
| 141 | + e2.Emit(); // no output |
| 142 | +} |
| 143 | +{% endhighlight %} |
| 144 | +The reason this produces no output is that the result of `Merge(e1, e2)` is destroyed after the call, as nobody takes ownership of it. |
| 145 | +Consequently, the observer is destroyed as well. |
| 146 | + |
| 147 | +To prevent this, the merged event stream could be kept in variable. |
| 148 | +Alternatively, we can make use of the fact that the observer handle takes ownership of its subject: |
| 149 | +{% highlight C++ %} |
| 150 | +auto obs = Observe(Merge(e1, e2), [] (Token) { |
| 151 | + cout << "Triggered" << endl; |
| 152 | +}); |
| 153 | + |
| 154 | +e1.Emit(); // output: Triggered |
| 155 | +e2.Emit(); // output: Triggered |
| 156 | +{% endhighlight %} |
0 commit comments