From 9b22f52b768cc75ef7d76acdf68b527c9b07e7eb Mon Sep 17 00:00:00 2001 From: Mateusz Tylman Date: Mon, 30 Mar 2026 15:18:31 +0200 Subject: [PATCH] fix(ios): dispatch Dictionary access to main thread in offline controllers Wrap progress and completion closures in DispatchQueue.main.async to prevent concurrent read/write access to handler dictionaries from MapboxCommon background threads, which caused EXC_BAD_ACCESS (SIGSEGV). Affected files: - TileStoreController.swift: tileRegionLoadProgressHandlers, tileRegionEstimateProgressHandlers - OfflineController.swift: progressHandlers Fixes #1116 --- .../Classes/Offline/OfflineController.swift | 10 +++++--- .../Classes/Offline/TileStoreController.swift | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/OfflineController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/OfflineController.swift index cedec2331..3a88c0686 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/OfflineController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/OfflineController.swift @@ -32,10 +32,14 @@ final class OfflineController: _OfflineManager { offlineManager.loadStylePack( for: styleURI, loadOptions: loadOptions) { [weak self] progress in - self?.progressHandlers[uri]?.eventSink?(progress.toFLTStylePackLoadProgress().toList()) + DispatchQueue.main.async { + self?.progressHandlers[uri]?.eventSink?(progress.toFLTStylePackLoadProgress().toList()) + } } completion: { [weak self] result in - executeOnMainThread(completion)(result.map { $0.toFLTStylePack() }) - self?.progressHandlers.removeValue(forKey: uri) + DispatchQueue.main.async { + completion(result.map { $0.toFLTStylePack() }) + self?.progressHandlers.removeValue(forKey: uri) + } } } diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/TileStoreController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/TileStoreController.swift index 48ae1b01a..67198cd97 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/TileStoreController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Offline/TileStoreController.swift @@ -26,11 +26,15 @@ final class TileStoreController: _TileStore { } tileStore.loadTileRegion(forId: id, loadOptions: loadOptions) { [weak self] progress in - guard let self else { return } - self.tileRegionLoadProgressHandlers[id]?.eventSink?(progress.toFLTTileRegionLoadProgress().toList()) + DispatchQueue.main.async { + guard let self else { return } + self.tileRegionLoadProgressHandlers[id]?.eventSink?(progress.toFLTTileRegionLoadProgress().toList()) + } } completion: { [weak self] result in - executeOnMainThread(completion)(result.map { $0.toFLTTileRegion() }) - self?.tileRegionLoadProgressHandlers.removeValue(forKey: id) + DispatchQueue.main.async { + completion(result.map { $0.toFLTTileRegion() }) + self?.tileRegionLoadProgressHandlers.removeValue(forKey: id) + } } } @@ -52,11 +56,15 @@ final class TileStoreController: _TileStore { forId: id, loadOptions: loadOptions, estimateOptions: estimateOptions.map(MapboxCommon.TileRegionEstimateOptions.init(fltValue:))) { [weak self] progress in - guard let self else { return } - self.tileRegionEstimateProgressHandlers[id]?.eventSink?(progress.toFLTTileRegionEstimateProgress().toList()) + DispatchQueue.main.async { + guard let self else { return } + self.tileRegionEstimateProgressHandlers[id]?.eventSink?(progress.toFLTTileRegionEstimateProgress().toList()) + } } completion: { [weak self] result in - executeOnMainThread(completion)(result.map { $0.toFLTTileRegionEstimateResult() }) - self?.tileRegionEstimateProgressHandlers.removeValue(forKey: id) + DispatchQueue.main.async { + completion(result.map { $0.toFLTTileRegionEstimateResult() }) + self?.tileRegionEstimateProgressHandlers.removeValue(forKey: id) + } } }