Redo error handling on iOS

Reviewed By: danzimm

Differential Revision: D5969343

fbshipit-source-id: 376984a6e959349260c54884c0b0b719f4c353d6
This commit is contained in:
Adam Ernst 2017-10-05 13:17:07 -07:00 committed by Facebook Github Bot
parent 1e3a8e2ed4
commit e87904cea5
5 changed files with 63 additions and 29 deletions

View File

@ -18,7 +18,7 @@ static NSRegularExpression *RCTJSStackFrameRegex()
static NSRegularExpression *_regex;
dispatch_once(&onceToken, ^{
NSError *regexError;
_regex = [NSRegularExpression regularExpressionWithPattern:@"^([^@]+)@(.*):(\\d+):(\\d+)$" options:0 error:&regexError];
_regex = [NSRegularExpression regularExpressionWithPattern:@"^(?:([^@]+)@)?(.*):(\\d+):(\\d+)$" options:0 error:&regexError];
if (regexError) {
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
}
@ -56,7 +56,9 @@ static NSRegularExpression *RCTJSStackFrameRegex()
return nil;
}
NSString *methodName = [line substringWithRange:[match rangeAtIndex:1]];
// methodName may not be present for e.g. anonymous functions
const NSRange methodNameRange = [match rangeAtIndex:1];
NSString *methodName = methodNameRange.location == NSNotFound ? nil : [line substringWithRange:methodNameRange];
NSString *file = [line substringWithRange:[match rangeAtIndex:2]];
NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]];
NSString *column = [line substringWithRange:[match rangeAtIndex:4]];
@ -69,7 +71,7 @@ static NSRegularExpression *RCTJSStackFrameRegex()
+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict
{
return [[self alloc] initWithMethodName:dict[@"methodName"]
return [[self alloc] initWithMethodName:RCTNilIfNull(dict[@"methodName"])
file:dict[@"file"]
lineNumber:[RCTNilIfNull(dict[@"lineNumber"]) integerValue]
column:[RCTNilIfNull(dict[@"column"]) integerValue]];

View File

@ -17,3 +17,6 @@
/** Registers a weakly-held observer of the Command+R reload key command. */
RCT_EXTERN void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener);
/** Triggers a reload for all current listeners. You shouldn't need to use this directly in most cases. */
RCT_EXTERN void RCTTriggerReloadCommandListeners(void);

View File

@ -9,11 +9,15 @@
#import "RCTReloadCommand.h"
#import "RCTAssert.h"
#import "RCTKeyCommands.h"
/** main queue only */
static NSHashTable<id<RCTReloadListener>> *listeners;
void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
{
static NSHashTable<id<RCTReloadListener>> *listeners;
RCTAssertMainQueue(); // because registerKeyCommandWithInput: must be called on the main thread
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
listeners = [NSHashTable weakObjectsHashTable];
@ -21,17 +25,19 @@ void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
modifierFlags:UIKeyModifierCommand
action:
^(__unused UIKeyCommand *command) {
NSArray<id<RCTReloadListener>> *copiedListeners;
@synchronized (listeners) { // avoid mutation-while-enumerating
copiedListeners = [listeners allObjects];
}
for (id<RCTReloadListener> l in copiedListeners) {
[l didReceiveReloadCommand];
}
RCTTriggerReloadCommandListeners();
}];
});
[listeners addObject:listener];
}
@synchronized (listeners) {
[listeners addObject:listener];
void RCTTriggerReloadCommandListeners(void)
{
RCTAssertMainQueue();
// Copy to protect against mutation-during-enumeration.
// If listeners hasn't been initialized yet we get nil, which works just fine.
NSArray<id<RCTReloadListener>> *copiedListeners = [listeners allObjects];
for (id<RCTReloadListener> l in copiedListeners) {
[l didReceiveReloadCommand];
}
}

View File

@ -13,6 +13,8 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTErrorCustomizer.h>
@class RCTJSStackFrame;
@interface RCTRedBox : NSObject <RCTBridgeModule>
- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer;
@ -22,9 +24,17 @@
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack;
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack;
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack;
- (void)dismiss;
/** Overrides bridge.bundleURL. Modify on main thread only. You shouldn't need to use this. */
@property (nonatomic, strong) NSURL *overrideBundleURL;
/** Overrides the default behavior of calling [bridge reload] on reload. You shouldn't need to use this. */
@property (nonatomic, strong) dispatch_block_t overrideReloadAction;
@end
/**

View File

@ -255,7 +255,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
}
cell.textLabel.text = stackFrame.methodName;
cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
if (stackFrame.file) {
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
} else {
@ -375,7 +375,7 @@ RCT_EXPORT_MODULE()
- (void)showErrorMessage:(NSString *)message
{
[self showErrorMessage:message withStack:nil isUpdate:NO];
[self showErrorMessage:message withParsedStack:nil isUpdate:NO];
}
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
@ -383,36 +383,42 @@ RCT_EXPORT_MODULE()
[self showErrorMessage:message withDetails:details stack:nil];
}
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<id> *)stack {
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<RCTJSStackFrame *> *)stack {
NSString *combinedMessage = message;
if (details) {
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
}
[self showErrorMessage:combinedMessage withStack:stack isUpdate:NO];
[self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO];
}
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
{
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
[self showErrorMessage:message withStack:stack isUpdate:NO];
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
{
[self showErrorMessage:message withStack:stack isUpdate:NO];
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO];
}
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
{
[self showErrorMessage:message withStack:stack isUpdate:YES];
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES];
}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<id> *)stack isUpdate:(BOOL)isUpdate
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
{
if (![[stack firstObject] isKindOfClass:[RCTJSStackFrame class]]) {
stack = [RCTJSStackFrame stackFramesWithDictionaries:stack];
}
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
}
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
{
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
}
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_window) {
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
@ -441,7 +447,8 @@ RCT_EXPORT_METHOD(dismiss)
- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame
{
if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL;
if (![bundleURL.scheme hasPrefix:@"http"]) {
RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
return;
}
@ -449,7 +456,7 @@ RCT_EXPORT_METHOD(dismiss)
NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
NSMutableURLRequest *request = [NSMutableURLRequest new];
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:_bridge.bundleURL];
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL];
request.HTTPMethod = @"POST";
request.HTTPBody = stackFrameJSON;
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
@ -460,7 +467,11 @@ RCT_EXPORT_METHOD(dismiss)
- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow
{
[_bridge reload];
if (_overrideReloadAction) {
_overrideReloadAction();
} else {
[_bridge reload];
}
[self dismiss];
}
@ -487,6 +498,8 @@ RCT_EXPORT_METHOD(dismiss)
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack {}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack {}
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack {}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate {}
- (void)dismiss {}