SDK Setup Guides
...
Quick Tips for Swift
Avoiding Excessive Memory Consumption
ditto's documents have value semantics by design this means that whenever you get a dittodocument from the ditto api, it's referencing an individual copy of the corresponding document data in memory, plus quite a bit of bookkeeping to keep the memory footprint low, it is therefore crucial to release those blobs of data as early as possible , freeing the claimed memory space special care needs to be taken whenever you spread the work across multiple queues or async apis it is very easy to end up with a lot of work items on a queue, each holding on to large amounts of data, such as big arrays of ditto documents this is not always obvious and leads to mysterious excessive memory consumption, eventually resulting in an out of memory crash, especially on mobile devices live queries live queries are particularly prone to this problem consider the following typical example // bad self livequery = ditto store collection("a") find("store id == 'abc123'") observelocal { \[weak self] documents, event in self? documentprocessingqueue async { print("\[info] processing \\(documents count) changed documents ") // inspect the changed documents, update ui state, etc } } depending on the amount of documents in the store and especially the rate at which those are updated, that observation callback might be called many times, each time with a fresh copy of the document data (due to value semantics) held in memory each of those dispatches will hold on to these document copies until the work item has a chance to run if the documentprocessingqueue is unable to process the documents and release them fast enough, more and more documents are accumulated in memory waiting to be processed, resulting in excessive memory use back pressure https //legacydocs ditto live/ios/quick tips/avoiding excessive memory consumption#back pressure to deal with these situations, all of our apis prone to this problem have more advanced variants allowing you to control the rate at which those callbacks are called this mechanism is commonly referred to as back pressure (see for example https //tanaschita com/20211205 back pressure in combine and https //medium com/@jayphelps/backpressure explained the flow of data through software 2350b3e77ce7 ) here is a much safer and more efficient way to implement the example above // better self livequery = ditto store collection("a") find("store id == 'abc123'") observelocalwithnextsignal { \[weak self] documents, event, signalnext in self? documentprocessingqueue async { print("\[info] processing \\(documents count) changed documents ") // inspect the changed documents, update ui state, etc // tell ditto we are done and ready to process the next batch signalnext() } } since this particular pattern is so common, ditto offers a convenient variant; however, it requires all work to be performed within the callback without dispatching onto different queues or using any async api that would hold on to the documents // convenient self livequery = ditto store collection("a") find("store id == 'abc123'") observelocal(deliveron self documentprocessingqueue) { \[weak self] documents, event in print("\[info] processing \\(documents count) changed documents ") // inspect the changed documents, update ui state, etc // important all work needs to be performed within this block without // dispatching onto different queues or using any async apis // in this case, ditto will internally call `signalnext` for us when // this block finishes } } since this particular pattern is so common, ditto offers a convenient variant but it requires all work to be performed within the callback without dispatching onto different queues or using any async api that would hold on to the documents // versatile self livequery = ditto store collection("a") find("store id == 'abc123'") observelocalwithnextsignal(deliveron self livequeryqueue) { \[weak self] documents, event, signalnext in // whenever we have to use asynchronous api that will hold on to the // documents, ditto won't know when it's safe to deliver the next batch of // changes, so we have to tell it explicitly by calling `signalnext()` self? processdocumentsasynchronously(documents) { signalnext() } } general rule https //legacydocs ditto live/ios/quick tips/avoiding excessive memory consumption#rule of thumb all of this boils down to the following general rules use observelocal(deliveron ) only if the received documents including the event are processed and can be released within that callback without dispatching onto other queues or using any async apis otherwise, use observelocalwithnextsignal(deliveron ) and call the dittosignalnext block after the received documents are fully processed and can be released this of course doesn't mean that you can never keep a reference to the documents and use or operate on them later on in fact, a typical use case would be to always keep the latest set of documents returned by a (live) query to display them in the ui or use otherwise the important thing is to control the rate at which those are delivered and let ditto know when you are ready to receive the next batch this rule of thumb can help with that implementation details the four variants shown in this article are as follows \ observelocal(handler ) \ observelocal(deliveron\ handler ) \ observelocalwithnextsignal(handler ) \ observelocalwithnextsignal(deliveron\ handler ) the former three are convenience methods and are implemented in terms of the latter https //legacydocs ditto live/ios/quick tips/avoiding excessive memory consumption#implementation details observelocal() { documents, event in // process documents } // equivalent to and implemented as observelocalwithnextsignal(deliveron main) { documents, event, signalnext in // process documents signalnext() } observelocal(deliveron somequeue) { documents, event, in // process documents } // equivalent to and implemented as observelocalwithnextsignal(deliveron somequeue) { documents, event, signalnext in // process documents signalnext() } observelocalwithnextsignal() { documents, event, signalnext in // process documents signalnext() } // equivalent to and implemented as observelocalwithnextsignal(deliveron main) { documents, event, signalnext in // process documents signalnext() }