From 8c4979e7a1ecb140b0b068d686b1ec6aab452121 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Fri, 26 Jan 2024 14:08:12 -0800 Subject: [PATCH] fix background access to UIKit in RCTAlertManager (#42684) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42684 This addressed threading issue like this: ``` Attempting to set an overrideUserInterfaceStyle from a background thread. Modifying a view controller from a background thread is not supported ``` Changelog: [iOS][Fixed] Fixed potential threading issues accessing UIKit from background in RCTAlertManager Reviewed By: philIip Differential Revision: D52999194 fbshipit-source-id: 8ce8a89ef932ca9b75cb93d3c9f102a6b0494580 --- .../React/CoreModules/RCTAlertManager.mm | 187 +++++++++--------- 1 file changed, 94 insertions(+), 93 deletions(-) diff --git a/packages/react-native/React/CoreModules/RCTAlertManager.mm b/packages/react-native/React/CoreModules/RCTAlertManager.mm index 085166e88aa15b..674bc389a94086 100644 --- a/packages/react-native/React/CoreModules/RCTAlertManager.mm +++ b/packages/react-native/React/CoreModules/RCTAlertManager.mm @@ -49,9 +49,11 @@ - (dispatch_queue_t)methodQueue - (void)invalidate { - for (UIAlertController *alertController in _alertControllers) { - [alertController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; - } + RCTExecuteOnMainQueue(^{ + for (UIAlertController *alertController in self->_alertControllers) { + [alertController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; + } + }); } /** @@ -101,101 +103,100 @@ - (void)invalidate } } - RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title - message:nil - preferredStyle:UIAlertControllerStyleAlert]; - - UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:args.userInterfaceStyle()]; - alertController.overrideUserInterfaceStyle = userInterfaceStyle; - - switch (type) { - case RCTAlertViewStylePlainTextInput: { - [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = NO; - textField.text = defaultValue; - textField.keyboardType = keyboardType; - }]; - break; - } - case RCTAlertViewStyleSecureTextInput: { - [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = RCTUIKitLocalizedString(@"Password"); - textField.secureTextEntry = YES; - textField.text = defaultValue; - textField.keyboardType = keyboardType; - }]; - break; + RCTExecuteOnMainQueue(^{ + RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + + UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:args.userInterfaceStyle()]; + alertController.overrideUserInterfaceStyle = userInterfaceStyle; + + switch (type) { + case RCTAlertViewStylePlainTextInput: { + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = NO; + textField.text = defaultValue; + textField.keyboardType = keyboardType; + }]; + break; + } + case RCTAlertViewStyleSecureTextInput: { + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = RCTUIKitLocalizedString(@"Password"); + textField.secureTextEntry = YES; + textField.text = defaultValue; + textField.keyboardType = keyboardType; + }]; + break; + } + case RCTAlertViewStyleLoginAndPasswordInput: { + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = RCTUIKitLocalizedString(@"Login"); + textField.text = defaultValue; + textField.keyboardType = keyboardType; + }]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = RCTUIKitLocalizedString(@"Password"); + textField.secureTextEntry = YES; + }]; + break; + } + case RCTAlertViewStyleDefault: + break; } - case RCTAlertViewStyleLoginAndPasswordInput: { - [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = RCTUIKitLocalizedString(@"Login"); - textField.text = defaultValue; - textField.keyboardType = keyboardType; - }]; - [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = RCTUIKitLocalizedString(@"Password"); - textField.secureTextEntry = YES; - }]; - break; - } - case RCTAlertViewStyleDefault: - break; - } - - alertController.message = message; - for (NSDictionary *button in buttons) { - if (button.count != 1) { - RCTLogError(@"Button definitions should have exactly one key."); - } - NSString *buttonKey = button.allKeys.firstObject; - NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]]; - UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault; - if ([buttonKey isEqualToString:cancelButtonKey]) { - buttonStyle = UIAlertActionStyleCancel; - } else if ([buttonKey isEqualToString:destructiveButtonKey]) { - buttonStyle = UIAlertActionStyleDestructive; - } - __weak RCTAlertController *weakAlertController = alertController; - - UIAlertAction *alertAction = - [UIAlertAction actionWithTitle:buttonTitle - style:buttonStyle - handler:^(__unused UIAlertAction *action) { - switch (type) { - case RCTAlertViewStylePlainTextInput: - case RCTAlertViewStyleSecureTextInput: - callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]); - [weakAlertController hide]; - break; - case RCTAlertViewStyleLoginAndPasswordInput: { - NSDictionary *loginCredentials = @{ - @"login" : [weakAlertController.textFields.firstObject text], - @"password" : [weakAlertController.textFields.lastObject text] - }; - callback(@[ buttonKey, loginCredentials ]); - [weakAlertController hide]; - break; + alertController.message = message; + + for (NSDictionary *button in buttons) { + if (button.count != 1) { + RCTLogError(@"Button definitions should have exactly one key."); + } + NSString *buttonKey = button.allKeys.firstObject; + NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]]; + UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault; + if ([buttonKey isEqualToString:cancelButtonKey]) { + buttonStyle = UIAlertActionStyleCancel; + } else if ([buttonKey isEqualToString:destructiveButtonKey]) { + buttonStyle = UIAlertActionStyleDestructive; + } + __weak RCTAlertController *weakAlertController = alertController; + + UIAlertAction *alertAction = + [UIAlertAction actionWithTitle:buttonTitle + style:buttonStyle + handler:^(__unused UIAlertAction *action) { + switch (type) { + case RCTAlertViewStylePlainTextInput: + case RCTAlertViewStyleSecureTextInput: + callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]); + [weakAlertController hide]; + break; + case RCTAlertViewStyleLoginAndPasswordInput: { + NSDictionary *loginCredentials = @{ + @"login" : [weakAlertController.textFields.firstObject text], + @"password" : [weakAlertController.textFields.lastObject text] + }; + callback(@[ buttonKey, loginCredentials ]); + [weakAlertController hide]; + break; + } + case RCTAlertViewStyleDefault: + callback(@[ buttonKey ]); + [weakAlertController hide]; + break; } - case RCTAlertViewStyleDefault: - callback(@[ buttonKey ]); - [weakAlertController hide]; - break; - } - }]; - [alertController addAction:alertAction]; - - if ([buttonKey isEqualToString:preferredButtonKey]) { - [alertController setPreferredAction:alertAction]; - } - } + }]; + [alertController addAction:alertAction]; - if (!_alertControllers) { - _alertControllers = [NSHashTable weakObjectsHashTable]; - } - [_alertControllers addObject:alertController]; + if ([buttonKey isEqualToString:preferredButtonKey]) { + [alertController setPreferredAction:alertAction]; + } + } - dispatch_async(dispatch_get_main_queue(), ^{ + if (!self->_alertControllers) { + self->_alertControllers = [NSHashTable weakObjectsHashTable]; + } + [self->_alertControllers addObject:alertController]; [alertController show:YES completion:nil]; }); }