Skip to content

Conversation

@PRESIDENT810
Copy link

@PRESIDENT810 PRESIDENT810 commented Sep 4, 2025

Description

In our App, the deprecated APIs (RNMBXMapView.mapView & RNMBXMapView.mapboxMap) cause the app to crash because rnmapbox is trying to unwrap them while they are still nil.

This PR removes those two deprecated APIs and changes all their usage to the safe withMapView & withMapboxMap API calls. After this fix our app no longer crashes.

Checklist

  • I've read CONTRIBUTING.md
  • I updated the doc/other generated code with running yarn generate in the root folder
  • I have tested the new feature on /example app.
    • In V11 mode/ios
    • In New Architecture mode/ios
    • [] In V11 mode/android
    • [] In New Architecture mode/android
  • I added/updated a sample - if a new feature was implemented (/example)

Screenshot OR Video

Component to reproduce the issue you're fixing

Kinda hard since the crash is triggered by an edge case in our app and we don't know how to create a minimal reproducing example yet but after I patched this package our app no longer crashes.

In our App, the deprecated APIs (`RNMBXMapView.mapView` &
`RNMBXMapView.mapboxMap`) cause the app to crash because rnmapbox is
trying to unwrap them while they are still `nil`.

This PR removes those two deprecated APIs and changes all their usage to
the safe `withMapView` & `withMapboxMap` API calls.
@PRESIDENT810 PRESIDENT810 temporarily deployed to CI with Mapbox Tokens September 4, 2025 22:10 — with GitHub Actions Inactive
@PRESIDENT810 PRESIDENT810 temporarily deployed to CI with Mapbox Tokens September 4, 2025 22:10 — with GitHub Actions Inactive
@PRESIDENT810 PRESIDENT810 temporarily deployed to CI with Mapbox Tokens September 4, 2025 22:10 — with GitHub Actions Inactive
@PRESIDENT810 PRESIDENT810 temporarily deployed to CI with Mapbox Tokens September 4, 2025 22:10 — with GitHub Actions Inactive
@PRESIDENT810 PRESIDENT810 changed the title feat: deprecate RNMBXMapView's mapView & mapboxMap APIs feat: remove deprecated RNMBXMapView's mapView & mapboxMap APIs Sep 4, 2025
@mfazekas
Copy link
Contributor

mfazekas commented Sep 5, 2025

@PRESIDENT810 thanks much for the PR

Unfortunately it's a bit complicated. Like in case of remove? withMapView could keep stuff alive and create a leak. So one thing we have to be carefully withMapView is leaks the other is concurrency, as the code will be called from other thread. And can cause deadlock and other issues.

Do you perhaps has stack traces? So we can address those cases, where you hit by the nil mapboxMap?

@PRESIDENT810
Copy link
Author

PRESIDENT810 commented Sep 5, 2025

@mfazekas Here's the stacktrace

          Crashed: com.apple.main-thread
0  <our app>                       0x2591ccc closure #5 in RNMBXMapView.setupEvents() + 1117 (RNMBXMapView.swift:1117)
1  <our app>                       0x258e554 specialized closure #1 in RNMBXMapView.onEvery<A>(event:handler:) + 305072
2  <our app>                       0x2597c38 partial apply for specialized closure #1 in RNMBXMapView.onNext<A>(event:handler:) + 343700 (<compiler-generated>:343700)
3  MapboxMaps                     0xb03cc closure #1 in MapboxObservable.onNext<A>(event:handler:) + 84
4  MapboxMaps                     0xb1950 partial apply for closure #1 in MapboxObservable.onNext<A>(event:handler:) + 20 (<compiler-generated>:20)
5  MapboxMaps                     0xb0524 @objc MapboxObservable.BlockObserver.notify(for:) + 166 (MapboxObservable.swift:166)
6  MapboxCoreMaps                 0x297f4 mapbox::bindgen::mapbox::maps::Observer::notify(mapbox::maps::Event const&) + 52
7  MapboxCoreMaps                 0x95644 mapbox::maps::ObserverWrapper::notify(mbgl::ObservableEvent const&) + 692
8  MapboxCoreMaps                 0x560d18 std::__1::__function::__func<mbgl::Observable::dispatchEvent(mbgl::ObservableEvent const&, mbgl::DispatchMode)::$_0, std::__1::allocator<mbgl::Observable::dispatchEvent(mbgl::ObservableEvent const&, mbgl::DispatchMode)::$_0>, void ()>::operator()() + 156
9  MapboxCommon                   0x4dc98 mapbox::common::platform::taskScope() + 1360
10 CoreFoundation                 0xf92c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
11 CoreFoundation                 0xf744 __CFRunLoopDoSource0 + 172
12 CoreFoundation                 0xf604 __CFRunLoopDoSources0 + 332
13 CoreFoundation                 0xff20 __CFRunLoopRun + 840
14 CoreFoundation                 0x11adc CFRunLoopRunSpecific + 572
15 GraphicsServices               0x1454 GSEventRunModal + 168
16 UIKitCore                      0x135274 -[UIApplication _run] + 816
17 UIKitCore                      0x100a28 UIApplicationMain + 336
18 <our app>                       0xf9b4 main + 36 (main.m:36)

I can see the crash is caused by 5th closure in RNMBXMapView.setupEvents with "Swift runtime failure: Unexpectedly found nil while implicitly unwrapping an optional value" but I can't see what triggers it.

For leaks, I think I used [weak self] and tried my best to avoid circular reference here, and for concurrency, I think the callers to withMapView & withMapboxMap will not wait for anything. If RNMBXMapView._mapView is nil, then the closure is appended to waiters and the function returns immediately. It's basically promising to execute that closure as soon as _mapView is ready, and when it's ready, closures in waiter will execute serially, which I don't think would cause deadlocks.

@PRESIDENT810
Copy link
Author

ping @mfazekas
We've been using this patch in prod for a several weeks with good results - would love if this could merge to main so that we don't have to maintain our own patch. assuming others would benefit from this as well; happy to make more improvements in the future


guard let mapboxMap = map.mapboxMap else {
return false
map.withMapboxMap { [weak self] _mapboxMap in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this. Maybe we should store the style we've added ourself to? Doesn't make much sense to wait for a style here. It could be other style, then we've added to, so...

Also not sure if a race like this is possible:

  1. item added to map with style A
  2. style removed from map
  3. item tries to removeFromMap so it's waits for style
  4. style B is added to map
  5. item is added to style B
  6. withMapboxMap is executed item removed from style B

this can't happen as 6.) will be called before 4.) but it's something I need to think about on every change like this.

if let center = camera.center {
try center.validate()
}
map.withMapView { _mapView in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

map.mapView.viewport.removeStatusObserver(self)
map.withMapView { _mapView in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the other remove, we don't want to try to remove from other mapViewpot we've added ourselves to

self.map = map
if let mapView = map.mapView {
installCustomeLocationProviderIfNeeded(mapView: mapView)
map.withMapView{ _mapView in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have a valid style, we should have a map too. Maybe add a map parameter as well?!

source.url = url
self.doUpdate { (style) in
try! style.setSourceProperty(for: id, property: "url", value: url)
self.map?.withMapboxMap { [weak self] _mapboxMap in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same concern here as well. We've added ourself to style. So we don't want to wait for it again. Maybe we should save the style here as well

resolver(["data": NSNumber(value: result)])
} else {
resolver(nil)
view.withMapboxMap { [weak view] _mapboxMap in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

])
case .failure(let error):
rejecter("queryRenderedFeaturesInRect", "failed to query features", error)
map.withMapboxMap { [weak map] _mapboxMap in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

rejecter(
"querySourceFeatures",
"failed to query source features: \(error.localizedDescription)", error)
map.withMapboxMap { _mapboxMap in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@mfazekas
Copy link
Contributor

@PRESIDENT810 thanks again, and sorry for taking this long.

So generally there is 2 cases in the changes.
1.) Things like MapView.queryRenderedFeaturesInRect: withMapboxMap is good here, as we might have called queryRenderedFeaturesInRect before the map init has finished. Waiting for an underlying map view is great here.
2.) Components added to a specific map/style - I think they'd be better to store the map/style. I don't like withMapboxMap there as it might mean a different map/style we've already added the component.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants