Skip to content

Commit 3f621df

Browse files
philIipfacebook-github-bot
authored andcommitted
migrate RCTActionSheetManager to handle synchronous void method execution
Summary: Changelog: [Internal] in the future, all void native module methods will execute synchronously. currently, many modules override the methodQueue selector to return the main queue so their async methods will be executed on the main thread by our infra. now that void methods are executing synchronously, this override will be ignored, thus causing unpredictable behavior for those methods that do depend on being run on main thread to behave correctly. the migration in this stack will prevent bugs caused by this behavioral change by explicitly dispatching execution onto the main thread. Reviewed By: mdvacca Differential Revision: D50635827 fbshipit-source-id: 384ee2f0237a49dc4f50e4171092c864f2f55327
1 parent ac7b158 commit 3f621df

1 file changed

Lines changed: 138 additions & 138 deletions

File tree

packages/react-native/React/CoreModules/RCTActionSheetManager.mm

Lines changed: 138 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ + (BOOL)requiresMainQueueSetup
4646

4747
@synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;
4848

49-
- (dispatch_queue_t)methodQueue
50-
{
51-
return dispatch_get_main_queue();
52-
}
53-
5449
- (void)presentViewController:(UIViewController *)alertController
5550
onParentViewController:(UIViewController *)parentViewController
5651
anchorViewTag:(NSNumber *)anchorViewTag
@@ -99,100 +94,102 @@ - (void)presentViewController:(UIViewController *)alertController
9994
NSNumber *destructiveButtonIndex = @-1;
10095
destructiveButtonIndices = @[ destructiveButtonIndex ];
10196
}
102-
103-
UIViewController *controller = RCTPresentedViewController();
10497
NSNumber *anchor = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil];
10598
UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];
10699
UIColor *cancelButtonTintColor =
107100
[RCTConvert UIColor:options.cancelButtonTintColor() ? @(*options.cancelButtonTintColor()) : nil];
101+
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
108102

109-
if (controller == nil) {
110-
RCTLogError(
111-
@"Tried to display action sheet but there is no application window. options: %@", @{
112-
@"title" : title,
113-
@"message" : message,
114-
@"options" : buttons,
115-
@"cancelButtonIndex" : @(cancelButtonIndex),
116-
@"destructiveButtonIndices" : destructiveButtonIndices,
117-
@"anchor" : anchor,
118-
@"tintColor" : tintColor,
119-
@"cancelButtonTintColor" : cancelButtonTintColor,
120-
@"disabledButtonIndices" : disabledButtonIndices,
121-
});
122-
return;
123-
}
103+
UIViewController *controller = RCTPresentedViewController();
124104

125-
/*
126-
* The `anchor` option takes a view to set as the anchor for the share
127-
* popup to point to, on iPads running iOS 8. If it is not passed, it
128-
* defaults to centering the share popup on screen without any arrows.
129-
*/
130-
NSNumber *anchorViewTag = anchor;
131-
132-
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
133-
message:message
134-
preferredStyle:UIAlertControllerStyleActionSheet];
135-
136-
NSInteger index = 0;
137-
bool isCancelButtonIndex = false;
138-
// The handler for a button might get called more than once when tapping outside
139-
// the action sheet on iPad. RCTResponseSenderBlock can only be called once so
140-
// keep track of callback invocation here.
141-
__block bool callbackInvoked = false;
142-
for (NSString *option in buttons) {
143-
UIAlertActionStyle style = UIAlertActionStyleDefault;
144-
if ([destructiveButtonIndices containsObject:@(index)]) {
145-
style = UIAlertActionStyleDestructive;
146-
} else if (index == cancelButtonIndex) {
147-
style = UIAlertActionStyleCancel;
148-
isCancelButtonIndex = true;
105+
dispatch_async(dispatch_get_main_queue(), ^{
106+
if (controller == nil) {
107+
RCTLogError(
108+
@"Tried to display action sheet but there is no application window. options: %@", @{
109+
@"title" : title,
110+
@"message" : message,
111+
@"options" : buttons,
112+
@"cancelButtonIndex" : @(cancelButtonIndex),
113+
@"destructiveButtonIndices" : destructiveButtonIndices,
114+
@"anchor" : anchor,
115+
@"tintColor" : tintColor,
116+
@"cancelButtonTintColor" : cancelButtonTintColor,
117+
@"disabledButtonIndices" : disabledButtonIndices,
118+
});
119+
return;
149120
}
150121

151-
NSInteger localIndex = index;
152-
UIAlertAction *actionButton = [UIAlertAction actionWithTitle:option
153-
style:style
154-
handler:^(__unused UIAlertAction *action) {
155-
if (!callbackInvoked) {
156-
callbackInvoked = true;
157-
[self->_alertControllers removeObject:alertController];
158-
callback(@[ @(localIndex) ]);
159-
}
160-
}];
161-
if (isCancelButtonIndex) {
162-
[actionButton setValue:cancelButtonTintColor forKey:@"titleTextColor"];
163-
}
164-
[alertController addAction:actionButton];
122+
/*
123+
* The `anchor` option takes a view to set as the anchor for the share
124+
* popup to point to, on iPads running iOS 8. If it is not passed, it
125+
* defaults to centering the share popup on screen without any arrows.
126+
*/
127+
NSNumber *anchorViewTag = anchor;
128+
129+
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
130+
message:message
131+
preferredStyle:UIAlertControllerStyleActionSheet];
132+
133+
NSInteger index = 0;
134+
bool isCancelButtonIndex = false;
135+
// The handler for a button might get called more than once when tapping outside
136+
// the action sheet on iPad. RCTResponseSenderBlock can only be called once so
137+
// keep track of callback invocation here.
138+
__block bool callbackInvoked = false;
139+
for (NSString *option in buttons) {
140+
UIAlertActionStyle style = UIAlertActionStyleDefault;
141+
if ([destructiveButtonIndices containsObject:@(index)]) {
142+
style = UIAlertActionStyleDestructive;
143+
} else if (index == cancelButtonIndex) {
144+
style = UIAlertActionStyleCancel;
145+
isCancelButtonIndex = true;
146+
}
165147

166-
index++;
167-
}
148+
NSInteger localIndex = index;
149+
UIAlertAction *actionButton = [UIAlertAction actionWithTitle:option
150+
style:style
151+
handler:^(__unused UIAlertAction *action) {
152+
if (!callbackInvoked) {
153+
callbackInvoked = true;
154+
[self->_alertControllers removeObject:alertController];
155+
callback(@[ @(localIndex) ]);
156+
}
157+
}];
158+
if (isCancelButtonIndex) {
159+
[actionButton setValue:cancelButtonTintColor forKey:@"titleTextColor"];
160+
}
161+
[alertController addAction:actionButton];
168162

169-
if (disabledButtonIndices) {
170-
for (NSNumber *disabledButtonIndex in disabledButtonIndices) {
171-
if ([disabledButtonIndex integerValue] < buttons.count) {
172-
[alertController.actions[[disabledButtonIndex integerValue]] setEnabled:false];
173-
} else {
174-
RCTLogError(
175-
@"Index %@ from `disabledButtonIndices` is out of bounds. Maximum index value is %@.",
176-
@([disabledButtonIndex integerValue]),
177-
@(buttons.count - 1));
178-
return;
163+
index++;
164+
}
165+
166+
if (disabledButtonIndices) {
167+
for (NSNumber *disabledButtonIndex in disabledButtonIndices) {
168+
if ([disabledButtonIndex integerValue] < buttons.count) {
169+
[alertController.actions[[disabledButtonIndex integerValue]] setEnabled:false];
170+
} else {
171+
RCTLogError(
172+
@"Index %@ from `disabledButtonIndices` is out of bounds. Maximum index value is %@.",
173+
@([disabledButtonIndex integerValue]),
174+
@(buttons.count - 1));
175+
return;
176+
}
179177
}
180178
}
181-
}
182179

183-
alertController.view.tintColor = tintColor;
184-
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
180+
alertController.view.tintColor = tintColor;
185181

186-
if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
187-
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
188-
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
189-
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
190-
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
191-
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
192-
}
182+
if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
183+
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
184+
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
185+
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
186+
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
187+
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
188+
}
193189

194-
[_alertControllers addObject:alertController];
195-
[self presentViewController:alertController onParentViewController:controller anchorViewTag:anchorViewTag];
190+
[self->_alertControllers addObject:alertController];
191+
[self presentViewController:alertController onParentViewController:controller anchorViewTag:anchorViewTag];
192+
});
196193
}
197194

198195
RCT_EXPORT_METHOD(dismissActionSheet)
@@ -201,9 +198,11 @@ - (void)presentViewController:(UIViewController *)alertController
201198
RCTLogWarn(@"Unable to dismiss action sheet");
202199
}
203200

204-
id _alertController = [_alertControllers lastObject];
205-
[_alertController dismissViewControllerAnimated:YES completion:nil];
206-
[_alertControllers removeLastObject];
201+
UIAlertController *alertController = [_alertControllers lastObject];
202+
dispatch_async(dispatch_get_main_queue(), ^{
203+
[alertController dismissViewControllerAnimated:YES completion:nil];
204+
[self->_alertControllers removeLastObject];
205+
});
207206
}
208207

209208
RCT_EXPORT_METHOD(showShareActionSheetWithOptions
@@ -218,68 +217,69 @@ - (void)presentViewController:(UIViewController *)alertController
218217

219218
NSMutableArray<id> *items = [NSMutableArray array];
220219
NSString *message = options.message();
221-
if (message) {
222-
[items addObject:message];
223-
}
224220
NSURL *URL = [RCTConvert NSURL:options.url()];
225-
if (URL) {
226-
if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
227-
NSError *error;
228-
NSData *data = [NSData dataWithContentsOfURL:URL options:(NSDataReadingOptions)0 error:&error];
229-
if (!data) {
230-
failureCallback(@[ RCTJSErrorFromNSError(error) ]);
231-
return;
232-
}
233-
[items addObject:data];
234-
} else {
235-
[items addObject:URL];
236-
}
237-
}
238-
if (items.count == 0) {
239-
RCTLogError(@"No `url` or `message` to share");
240-
return;
241-
}
242-
243-
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items
244-
applicationActivities:nil];
245-
246221
NSString *subject = options.subject();
247-
if (subject) {
248-
[shareController setValue:subject forKey:@"subject"];
249-
}
250-
251222
NSArray *excludedActivityTypes =
252223
RCTConvertOptionalVecToArray(options.excludedActivityTypes(), ^id(NSString *element) {
253224
return element;
254225
});
255-
if (excludedActivityTypes) {
256-
shareController.excludedActivityTypes = excludedActivityTypes;
257-
}
226+
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
227+
NSNumber *anchorViewTag = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil];
228+
UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];
258229

259-
UIViewController *controller = RCTPresentedViewController();
260-
shareController.completionWithItemsHandler =
261-
^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
262-
if (activityError) {
263-
failureCallback(@[ RCTJSErrorFromNSError(activityError) ]);
264-
} else if (completed || activityType == nil) {
265-
successCallback(@[ @(completed), RCTNullIfNil(activityType) ]);
230+
dispatch_async(dispatch_get_main_queue(), ^{
231+
if (message) {
232+
[items addObject:message];
233+
}
234+
if (URL) {
235+
if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
236+
NSError *error;
237+
NSData *data = [NSData dataWithContentsOfURL:URL options:(NSDataReadingOptions)0 error:&error];
238+
if (!data) {
239+
failureCallback(@[ RCTJSErrorFromNSError(error) ]);
240+
return;
266241
}
267-
};
268-
269-
NSNumber *anchorViewTag = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil];
270-
shareController.view.tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];
242+
[items addObject:data];
243+
} else {
244+
[items addObject:URL];
245+
}
246+
}
247+
if (items.count == 0) {
248+
RCTLogError(@"No `url` or `message` to share");
249+
return;
250+
}
271251

272-
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
252+
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items
253+
applicationActivities:nil];
254+
if (subject) {
255+
[shareController setValue:subject forKey:@"subject"];
256+
}
257+
if (excludedActivityTypes) {
258+
shareController.excludedActivityTypes = excludedActivityTypes;
259+
}
273260

274-
if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
275-
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
276-
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
277-
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
278-
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
279-
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
280-
}
261+
UIViewController *controller = RCTPresentedViewController();
262+
shareController.completionWithItemsHandler =
263+
^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
264+
if (activityError) {
265+
failureCallback(@[ RCTJSErrorFromNSError(activityError) ]);
266+
} else if (completed || activityType == nil) {
267+
successCallback(@[ @(completed), RCTNullIfNil(activityType) ]);
268+
}
269+
};
270+
271+
shareController.view.tintColor = tintColor;
272+
273+
if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
274+
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
275+
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
276+
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
277+
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
278+
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
279+
}
281280

282-
[self presentViewController:shareController onParentViewController:controller anchorViewTag:anchorViewTag];
281+
[self presentViewController:shareController onParentViewController:controller anchorViewTag:anchorViewTag];
282+
});
283283
}
284284

285285
- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params

0 commit comments

Comments
 (0)