Skip to content

Commit e708b7b

Browse files
fix-ios-wrong-thread-usage (#104)
* reformat code * resolve conflicts * reformat code * reformat code
1 parent 196b6da commit e708b7b

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

arcgis_map_sdk_ios/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
.pub-cache/
3030
.pub/
3131
build/
32+
.build/
3233
pubspec.lock
3334

3435
# Android related

arcgis_map_sdk_ios/ios/arcgis_map_sdk_ios/Sources/arcgis_map_sdk_ios/ArcgisMapView.swift

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -616,16 +616,50 @@ class ArcgisMapView: NSObject, FlutterPlatformView {
616616
}
617617
}
618618

619+
/// Cleans up Flutter channels and view model references.
620+
///
621+
/// ## Threading Considerations
622+
///
623+
/// Flutter channel operations (`setStreamHandler`, `setMethodCallHandler`) **must** be called
624+
/// on the main thread. However, `deinit` can be triggered from a background thread when:
625+
///
626+
/// 1. An async `Task` (e.g., in `onStartLocationDisplayDataSource`) captures `[weak self]`
627+
/// 2. The Task completes on a background thread (Swift's default async executor)
628+
/// 3. If `self` has no other strong references, ARC deallocates it on that background thread
629+
/// 4. This calls `deinit` on the background thread
630+
///
631+
/// If we call `setStreamHandler(nil)` from a background thread, Flutter's
632+
/// `PlatformMessageHandlerIos::SetMessageHandler` detects the thread violation and
633+
/// crashes via `fml::KillProcess()`.
634+
///
635+
/// ## Solution
636+
///
637+
/// We check the current thread and dispatch to main if necessary. Channel references
638+
/// are captured locally since `self` properties become inaccessible during `deinit`.
619639
deinit {
620640
mapContentView.viewModel.onScaleChanged = nil
621641
mapContentView.viewModel.onVisibleAreaChanged = nil
622642
mapContentView.viewModel.onLoadStatusChanged = nil
623643
mapContentView.viewModel.onViewInit = nil
624644
mapContentView.viewModel.mapViewProxy = nil
625645

626-
zoomEventChannel.setStreamHandler(nil)
627-
centerPositionEventChannel.setStreamHandler(nil)
628-
methodChannel.setMethodCallHandler(nil)
646+
// Capture channel references locally - self.* properties are inaccessible
647+
// after deinit begins, and we need them for the potential async dispatch.
648+
let zoomChannel = zoomEventChannel
649+
let centerChannel = centerPositionEventChannel
650+
let methodChan = methodChannel
651+
652+
if Thread.isMainThread {
653+
zoomChannel.setStreamHandler(nil)
654+
centerChannel.setStreamHandler(nil)
655+
methodChan.setMethodCallHandler(nil)
656+
} else {
657+
DispatchQueue.main.async {
658+
zoomChannel.setStreamHandler(nil)
659+
centerChannel.setStreamHandler(nil)
660+
methodChan.setMethodCallHandler(nil)
661+
}
662+
}
629663
}
630664
}
631665

@@ -819,7 +853,6 @@ extension Basemap.Style {
819853
return "osmNavigation"
820854
case .osmNavigationDark:
821855
return "osmNavigationDark"
822-
823856
}
824857
}
825858
}

0 commit comments

Comments
 (0)