Skip to content

Commit b416ca1

Browse files
Abbondanzofacebook-github-bot
authored andcommitted
Process Settings notifications off the posting thread (#58978)
Summary: `SettingsManager` cleans the entire user-defaults dictionary inline whenever a defaults change notification arrives. A write on the main thread therefore performs unrelated, potentially expensive work before returning. Put the asynchronous update path behind a default-off runtime gate. With the gate off, preserve synchronous snapshot cleaning and event delivery, every intermediate update, the default module queue, and the existing lifecycle registration. With the gate on, process notifications on a dedicated serial background method queue. Coalesce pending notifications to the latest defaults state, while allowing a notification during a snapshot to schedule a trailing update. Keep JavaScript mutations on the same queue and suppress their notifications at posting time. Drop queued work after invalidation without blocking teardown or retaining the module. The `settingsUpdated` event name and full JSON-cleaned payload remain unchanged. Enabled-mode rapid external changes may be reported as one latest-state update rather than separate intermediate transitions. Synchronous constants export is unchanged. Declare the Settings pod's feature-flags dependency so dynamic-framework builds can link the gate. Changelog: [iOS][Fixed] - Add an opt-in asynchronous Settings update path to avoid blocking user-defaults writers Differential Revision: D124504338
1 parent 1c6e0c7 commit b416ca1

23 files changed

Lines changed: 740 additions & 100 deletions

‎packages/react-native/Libraries/Settings/RCTSettingsManager.mm‎

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,24 @@
1111
#import <React/RCTBridge.h>
1212
#import <React/RCTConvert.h>
1313
#import <React/RCTEventDispatcherProtocol.h>
14+
#import <React/RCTInvalidating.h>
1415
#import <React/RCTUtils.h>
1516

17+
#include <react/featureflags/ReactNativeFeatureFlags.h>
18+
#include <atomic>
19+
1620
#import "RCTSettingsPlugins.h"
1721

18-
@interface RCTSettingsManager () <NativeSettingsManagerSpec>
22+
@interface RCTSettingsManager () <NativeSettingsManagerSpec, RCTInvalidating>
1923
@end
2024

2125
@implementation RCTSettingsManager {
22-
BOOL _ignoringUpdates;
26+
std::atomic<bool> _ignoringUpdates;
27+
std::atomic<bool> _updateScheduled;
28+
std::atomic<bool> _invalidated;
29+
bool _useAsyncUpdates;
2330
NSUserDefaults *_defaults;
31+
dispatch_queue_t _methodQueue;
2432
}
2533

2634
@synthesize moduleRegistry = _moduleRegistry;
@@ -40,7 +48,14 @@ - (instancetype)init
4048
- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults
4149
{
4250
if ((self = [super init]) != nullptr) {
51+
_ignoringUpdates = false;
52+
_updateScheduled = false;
53+
_invalidated = false;
4354
_defaults = defaults;
55+
_useAsyncUpdates = facebook::react::ReactNativeFeatureFlags::enableAsyncSettingsManagerUpdatesIOS();
56+
if (_useAsyncUpdates) {
57+
_methodQueue = dispatch_queue_create("com.facebook.react.SettingsManager", DISPATCH_QUEUE_SERIAL);
58+
}
4459

4560
[[NSNotificationCenter defaultCenter] addObserver:self
4661
selector:@selector(userDefaultsDidChange:)
@@ -61,18 +76,71 @@ - (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults
6176
{.settings = RCTJSONClean([_defaults dictionaryRepresentation])});
6277
}
6378

79+
- (dispatch_queue_t)methodQueue
80+
{
81+
return _methodQueue;
82+
}
83+
84+
- (BOOL)respondsToSelector:(SEL)selector
85+
{
86+
if (selector == @selector(invalidate) && !_useAsyncUpdates) {
87+
return NO;
88+
}
89+
return [super respondsToSelector:selector];
90+
}
91+
92+
- (void)invalidate
93+
{
94+
if (!_useAsyncUpdates) {
95+
return;
96+
}
97+
_invalidated = true;
98+
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:_defaults];
99+
}
100+
64101
- (void)userDefaultsDidChange:(NSNotification *)note
65102
{
66103
if (_ignoringUpdates) {
67104
return;
68105
}
69106

107+
if (!_useAsyncUpdates) {
108+
NSDictionary *settings = RCTJSONClean([_defaults dictionaryRepresentation]);
109+
#pragma clang diagnostic push
110+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
111+
[[_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"settingsUpdated" body:settings];
112+
#pragma clang diagnostic pop
113+
return;
114+
}
115+
116+
if (_invalidated || _updateScheduled.exchange(true)) {
117+
return;
118+
}
119+
120+
__weak RCTSettingsManager *weakSelf = self;
121+
dispatch_async(_methodQueue, ^{
122+
RCTSettingsManager *strongSelf = weakSelf;
123+
if (strongSelf == nil) {
124+
return;
125+
}
126+
127+
// Clear before reading so a notification during the snapshot schedules a trailing update.
128+
strongSelf->_updateScheduled = false;
129+
if (strongSelf->_invalidated) {
130+
return;
131+
}
132+
133+
NSDictionary *settings = RCTJSONClean([strongSelf->_defaults dictionaryRepresentation]);
134+
if (strongSelf->_invalidated) {
135+
return;
136+
}
137+
70138
#pragma clang diagnostic push
71139
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
72-
[[_moduleRegistry moduleForName:"EventDispatcher"]
73-
sendDeviceEventWithName:@"settingsUpdated"
74-
body:RCTJSONClean([_defaults dictionaryRepresentation])];
140+
[[strongSelf->_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"settingsUpdated"
141+
body:settings];
75142
#pragma clang diagnostic pop
143+
});
76144
}
77145

78146
/**

‎packages/react-native/Libraries/Settings/React-RCTSettings.podspec‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Pod::Spec.new do |s|
4444
s.dependency "React-jsi"
4545
s.dependency "React-Core/RCTSettingsHeaders"
4646

47+
add_dependency(s, "React-featureflags")
4748
add_dependency(s, "React-RCTFBReactNativeSpec")
4849
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
4950
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])

0 commit comments

Comments
 (0)