fix: tear down iOS NavView on Fabric invalidate so the map releases location - #632
Conversation
…ocation NavView registers its NavViewController in NavViewModule's static registry, and the controller holds the view back through its _viewCallbacks ivar, so the two keep each other alive after unmount: the only release path was -dealloc, which that cycle prevents. The leaked GMSMapView stays attached to the navigation session and keeps a kCLLocationAccuracyBestForNavigation CLLocationManager subscribed for the rest of the process. Tear down from -invalidate instead — the hook Fabric calls for unmounted views that are not pooled (shouldBeRecycled is NO) — using a nativeID cached at registration time rather than props that teardown cannot rely on, and declare NavViewController.cleanup in the header so the map view and the callback back-reference go with it. Fixes googlemaps#631
|
This fix looks solid for the unmount-while-session-alive case, and we can confirm the One gap I think is still open after this PR: it only releases the map view when the view unmounts ( // NavViewModule.mm — unchanged by this PR
- (void)navigationSessionDestroyed {
for (NavViewController *viewController in [NavViewModule viewControllersRegistry].allValues) {
[viewController detachFromNavigationSession];
}
}
We hit exactly this in a repro that calls Would it make sense to also release navigation-type controllers eagerly from // NavViewController.mm — track whether this is a navigation view (vs. a map-only view)
- (void)setMapViewType:(MapViewType)mapViewType {
...
*_mapViewType = mapViewType;
self.isNavigationView = (mapViewType == NAVIGATION);
}
// NavViewModule.mm
- (void)navigationSessionDestroyed {
NSMutableDictionary<NSString *, NavViewController *> *registry =
[NavViewModule viewControllersRegistry];
for (NSString *nativeID in [registry.allKeys copy]) {
NavViewController *viewController = registry[nativeID];
if (viewController.isNavigationView) {
// Release immediately instead of waiting for the (possibly delayed) unmount.
[viewController cleanup];
[registry removeObjectForKey:nativeID];
} else {
[viewController detachFromNavigationSession];
}
}
} |
|
@christian-apollo Thank you for your contribution. Could you please test the example app that is bundled with the SDK and follow the reproduction flow that you shared?
That being said I was able to see the memory leak before and after your fix it's gone. 👍 |
illuminati1911
left a comment
There was a problem hiding this comment.
This looks good to me and it solves the issue. Please remove all the comments from the code though as they are overly verbose and the code is mostly self-explanatory as well.
Hey @yuto-hagatch. The SDK is designed so that the navigation session and map view operate independently of each other. They can work together, but closing the map does not imply an intention to end the navigation session, and ending the navigation session does not imply an intention to close the map, even if the map was previously used for navigation. |
Removes the comments added alongside the -invalidate teardown: the note on registeredNativeID, the rationale block above -invalidate, the cleanup note in unregisterView, and the header doc on NavViewController's cleanup.
|
@illuminati1911 Comments removed and pushed in 2f4fffd. The diff is now just the Thanks for confirming the leak is gone on the example app — and good catch that the example holds the navigation session independently of the page, which is what kept the status-bar indicator on until the explicit "clean up". |
|
@yuto-hagatch Thanks for digging into this, and for independently verifying the On the For the repro you describe — |
Fixes #631
On iOS (Fabric), unmounting a
NavigationViewleaks theNavViewControllerand itsGMSMapView:NavViewModule's static registry holds the controller strongly, and the controller holds theNavViewback strongly through its_viewCallbacksivar. The only teardown path isNavView's-dealloc, which that cycle prevents from ever running. The leaked map view stays attached to the navigation session and keeps akCLLocationAccuracyBestForNavigationCLLocationManagersubscribed for the rest of the process — the status-bar location indicator stays on with no map on screen and no guidance running, and with "Always" authorization iOS keeps feeding it in the background.Changes
NavView.mm: override-invalidate— the hook Fabric actually calls for unmounted views that are not pooled (shouldBeRecycledisNO) — and tear down from there.NavView.mm: cache thenativeIDat registration time in aregisteredNativeIDproperty, sounregisterViewno longer depends on_propsstill being readable at teardown.NavView.mm: inunregisterView, also call[_viewController cleanup]and nil the controller.cleanupalready exists and nils_viewCallbacks, which breaks both sides of the cycle so view and controller can deallocate.NavViewController.h: declare the existingcleanupmethod (previously only reachable from the controller's own-dealloc, i.e. never).Testing
We ship this exact change in production (Apollo Scooters app, RN 0.86, New Architecture) via
patch-package:NavigationView, background the app → location indicator stays on; Xcode memory graph shows theNavView↔NavViewControllercycle with theGMSMapViewalive.