SDK Setup Guides
...
Quick Tips for Swift
Preventing Accumulating Combine Publishers
note this is a general concept that isn't specific to ditto, but really towards all applications that might use combine or rxswift https //github com/getditto/samples/tree/master/combine switchtolatest https //www loom com/share/40e110dfecf54fcbbeb6c4e69537c055 a common issue we see in reactive apps is a failure to dispose of resources as conditions change perhaps your app sees a large accumulation of publishers that infinitely grow let's take a look at this common pitfall in a simple example let's say we have a collection of flights and each document has a structure like this \[ { " id" "123abc", "carrier" "ba", "number" 45, "from" "sfo", "to" "lhr" }, { " id" "456284a", "carrier" "ba", "number" 8942, "from" "sfo", "to" "lhr" }, { " id" "8sd24s", "carrier" "lh", "number" 8942, "from" "jfk", "to" "fra" } { " id" "79542", "carrier" "ua", "number" 1234, "from" "ord", "to" "mia" } // etc ] creating a memory leak with combine https //legacydocs ditto live/ios/quick tips/prevent accumulating publishers#creating a memory leak with combine now our application code has a viewmodel that creates live queries based on a change of the carrier property each time the carrier changes, we register a new livequerypublisher with the args \["carrier" carrier] query argument like all good combine best practices, we store the publisher references in a var cancellables = set\<anycancellables>() import dittoswift import combine import swiftui class viewmodel observableobject { @published var flights \[flight] = \[] @published var carrier string = "ba" var cancellables = set\<anycancellables>() init() { $carrier sink { carrier in dittomanager shared ditto store collection("flights") find("carrier == $args carrier", args \["carrier" carrier]) livequerypublisher() sink { (docs, ) in self flights = docs map({ try! $0 typed(as flight self) value }) } store(in \&self cancellables) } store(in \&cancellables) } } here's our problem, each time we change the carrier to a new value, you will notice that your app's memory usage will gradually rise it will be even worse if new documents change while this happens why is this? our app is to filter the @published var flights \[flight] variable to match the current carrier however, each time the carrier changes, we are registering a new livequerypublisher without disposing of the previous live query there lies the root cause of the accumulation of publishers a hacky fix https //legacydocs ditto live/ios/quick tips/prevent accumulating publishers#a hacky fix a hacky way to fix this is by storing the livequerypublisher() into another set of cancelables and disposing of them whenever the carrier changes do not include a sink within sink import dittoswift import combine import swiftui class viewmodel observableobject { @published var flights \[flight] = \[] @published var carrier string = "ba" var cancellables = set\<anycancellables>() // this was added var livequerycancellables = set\<anycancellables>() init() { $carrier sink { carrier in // cancel all the live queries when this value changes livequerycancellables removeall() dittomanager shared ditto store collection("flights") find("carrier == $args carrier", args \["carrier" carrier]) livequerypublisher() sink { (docs, ) in self flights = docs map({ try! $0 typed(as flight self) value }) } store(in \&self livequerycancellables) // store it in another set of cancellables } store(in \&cancellables) } } hooray! this solves the problem! you'll notice that there is now only 1 live query regardless of the carrier changes however, this has made the application more complex and hard to read, as every sink requires managing cancellable states if you add more live queries this gets complicated quite fast the preferred approach https //legacydocs ditto live/ios/quick tips/prevent accumulating publishers#the good way how do we maintain only livequerypublisher even if the carrier changes in the cleanest combine friendly way? let's see class solutionviewmodel observableobject { @published var carrier string = dittomanager carriers randomelement()! @published var flights \[flight] = \[] var cancellables = set\<anycancellable>() init() { $carrier removeduplicates() // 1 map({ carrier in // 2 return dittomanager shared ditto store collection("flights") find("carrier == $args carrier", args \["carrier" carrier]) livequerypublisher() }) switchtolatest() // 3 map({ (docs, ) in // 4 return docs map({ try! $0 typed(as flight self) value }) }) assign(to \\ flights, on self) // 5 store(in \&cancellables) // 6 } } let's review what we did (see the code comments for the correlated bullet numbers) we observe the carrier, but only if the values change by using removeduplicates() this means if the carrier value goes from as to as , it is fired only once but if the value goes from as to lh it's fired twice it will compare the new and the old value and emit once the values are different once we get the carrier , we map it to a livequerypublisher while feeding in the query arguments note that this map isn't returning documents, but a publisher this key difference is important in step 3 the next operator is the most critical, we add a switchtolatest() this operator will switch the chain of operators to the latest publisher (from the last map ) any previous publisher will be disposed of or canceled this is the operator that does all the work! we can now map the docs results of the livequerypublisher and decode them to flight instead of using sink , we assign the flights \[flight] to the self flights variable finally, we store the entire chain of commands as set\<anycancellable>() the summary use map to return a publisher and follow it immediately with switchtolatest() to ensure the publishers do not accumulate and that only one is registered down the chain of operators using both is not only helpful with ditto based apps but also any application that uses combine this will prevent quite a lot of memory leaks in the future if you are familiar with https //github com/reactivex/rxswift , map + switchtolatest is the same as flatmaplatest