From e4891e07e580a33d644c36e312e7ae7b5a9f3e2b Mon Sep 17 00:00:00 2001 From: OxMarco Date: Sun, 12 Jul 2026 17:48:07 +0200 Subject: [PATCH] fix(offline): keep OfflineManager alive until its async tasks complete `startLoading` creates a function-local `OfflineManager`, uses it to start `loadStylePack` and to vend a tileset descriptor, then returns. Nothing retains it: the progress and completion closures do not capture it, and `cancelables` holds the tasks rather than the manager. The manager is therefore deinitialized as soon as `startLoading` returns, while both asynchronous operations are still running and the Mapbox SDK still holds state tied to it. That aborts with the Swift runtime error "object deallocated with non-zero retain count" in a closure in `startLoading`. The existing DispatchGroup already marks the point at which both tasks are finished, so capture the manager in its `notify` block and hold it until then. --- ios/RNMBX/Offline/RNMBXOfflineModule.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ios/RNMBX/Offline/RNMBXOfflineModule.swift b/ios/RNMBX/Offline/RNMBXOfflineModule.swift index 105309565c..fdca5fa4d8 100644 --- a/ios/RNMBX/Offline/RNMBXOfflineModule.swift +++ b/ios/RNMBX/Offline/RNMBXOfflineModule.swift @@ -424,9 +424,17 @@ class RNMBXOfflineModule: RCTEventEmitter { } } - taskGroup.notify(queue: .main) { + // `threadedOfflineManager` is function-local, and neither the closures above + // nor `cancelables` (which holds the tasks, not the manager) retain it. Left + // alone it would be deinitialized as soon as `startLoading` returns, while + // the style pack load and the tileset descriptor it vended are still in + // flight, which aborts with "object deallocated with non-zero retain count". + // The task group already marks the point where both are done, so hold the + // manager until then. + taskGroup.notify(queue: .main) { [threadedOfflineManager] in self.tileRegionPacks[id]!.cancelables = [] self.tileRegionPacks[id]!.state = downloadError ? .inactive : .complete + withExtendedLifetime(threadedOfflineManager) {} } self.tileRegionPacks[id]!.cancelables = [task, stylePackTask]