Implemented thread control for exported methods
This commit is contained in:
parent
2b9aaac2ff
commit
ead0f2e020
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
@ -59,11 +59,10 @@
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure this test runs first (underscores sort early) otherwise the
|
// Make sure this test runs first because the other tests will tear out the rootView
|
||||||
// other tests will tear out the rootView
|
- (void)testAAA_RootViewLoadsAndRenders
|
||||||
- (void)test__RootViewLoadsAndRenders
|
|
||||||
{
|
{
|
||||||
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||||
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
|
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
|
||||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||||
BOOL foundElement = NO;
|
BOOL foundElement = NO;
|
||||||
|
@ -72,10 +71,8 @@
|
||||||
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
|
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
|
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
|
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
|
||||||
|
|
||||||
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
|
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||||
|
foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) {
|
||||||
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
|
|
||||||
if ([view respondsToSelector:@selector(attributedText)]) {
|
if ([view respondsToSelector:@selector(attributedText)]) {
|
||||||
NSString *text = [(id)view attributedText].string;
|
NSString *text = [(id)view attributedText].string;
|
||||||
if ([text isEqualToString:@"<View>"]) {
|
if ([text isEqualToString:@"<View>"]) {
|
||||||
|
@ -120,6 +117,7 @@
|
||||||
[_runner runTest:_cmd module:@"TabBarExample"];
|
[_runner runTest:_cmd module:@"TabBarExample"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure this test runs last
|
||||||
- (void)testZZZ_NotInRecordMode
|
- (void)testZZZ_NotInRecordMode
|
||||||
{
|
{
|
||||||
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
|
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
|
||||||
|
|
|
@ -34,92 +34,88 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
|
||||||
failureCallback:(RCTResponseSenderBlock)failureCallback
|
failureCallback:(RCTResponseSenderBlock)failureCallback
|
||||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
|
||||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
|
|
||||||
|
|
||||||
actionSheet.title = options[@"title"];
|
actionSheet.title = options[@"title"];
|
||||||
|
|
||||||
for (NSString *option in options[@"options"]) {
|
for (NSString *option in options[@"options"]) {
|
||||||
[actionSheet addButtonWithTitle:option];
|
[actionSheet addButtonWithTitle:option];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options[@"destructiveButtonIndex"]) {
|
if (options[@"destructiveButtonIndex"]) {
|
||||||
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
|
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
|
||||||
}
|
}
|
||||||
if (options[@"cancelButtonIndex"]) {
|
if (options[@"cancelButtonIndex"]) {
|
||||||
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
|
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
actionSheet.delegate = self;
|
actionSheet.delegate = self;
|
||||||
|
|
||||||
_callbacks[keyForInstance(actionSheet)] = successCallback;
|
_callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
|
||||||
|
|
||||||
UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window];
|
UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window];
|
||||||
if (appWindow == nil) {
|
if (appWindow == nil) {
|
||||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[actionSheet showInView:appWindow];
|
[actionSheet showInView:appWindow];
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||||
failureCallback:(RCTResponseSenderBlock)failureCallback
|
failureCallback:(RCTResponseSenderBlock)failureCallback
|
||||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
NSMutableArray *items = [NSMutableArray array];
|
||||||
NSMutableArray *items = [NSMutableArray array];
|
id message = options[@"message"];
|
||||||
id message = options[@"message"];
|
id url = options[@"url"];
|
||||||
id url = options[@"url"];
|
if ([message isKindOfClass:[NSString class]]) {
|
||||||
if ([message isKindOfClass:[NSString class]]) {
|
[items addObject:message];
|
||||||
[items addObject:message];
|
}
|
||||||
}
|
if ([url isKindOfClass:[NSString class]]) {
|
||||||
if ([url isKindOfClass:[NSString class]]) {
|
[items addObject:[NSURL URLWithString:url]];
|
||||||
[items addObject:[NSURL URLWithString:url]];
|
}
|
||||||
}
|
if ([items count] == 0) {
|
||||||
if ([items count] == 0) {
|
failureCallback(@[@"No `url` or `message` to share"]);
|
||||||
failureCallback(@[@"No `url` or `message` to share"]);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||||
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||||
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) {
|
||||||
if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) {
|
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
if (activityError) {
|
||||||
if (activityError) {
|
failureCallback(@[[activityError localizedDescription]]);
|
||||||
failureCallback(@[[activityError localizedDescription]]);
|
} else {
|
||||||
} else {
|
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
}
|
||||||
}
|
};
|
||||||
};
|
} else {
|
||||||
} else {
|
|
||||||
|
|
||||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||||
|
|
||||||
if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) {
|
if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) {
|
||||||
// Legacy iOS 7 implementation
|
// Legacy iOS 7 implementation
|
||||||
share.completionHandler = ^(NSString *activityType, BOOL completed) {
|
share.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||||
};
|
};
|
||||||
} else
|
} else
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{
|
{
|
||||||
// iOS 8 version
|
// iOS 8 version
|
||||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[ctrl presentViewController:share animated:YES completion:nil];
|
}
|
||||||
});
|
[ctrl presentViewController:share animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark UIActionSheetDelegate Methods
|
#pragma mark UIActionSheetDelegate Methods
|
||||||
|
|
||||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||||
{
|
{
|
||||||
NSString *key = keyForInstance(actionSheet);
|
NSString *key = RCTKeyForInstance(actionSheet);
|
||||||
RCTResponseSenderBlock callback = _callbacks[key];
|
RCTResponseSenderBlock callback = _callbacks[key];
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(@[@(buttonIndex)]);
|
callback(@[@(buttonIndex)]);
|
||||||
|
@ -133,7 +129,7 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||||
|
|
||||||
#pragma mark Private
|
#pragma mark Private
|
||||||
|
|
||||||
NS_INLINE NSString *keyForInstance(id instance)
|
static NSString *RCTKeyForInstance(id instance)
|
||||||
{
|
{
|
||||||
return [NSString stringWithFormat:@"%p", instance];
|
return [NSString stringWithFormat:@"%p", instance];
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,11 @@ RCT_EXPORT_MODULE()
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (dispatch_queue_t)methodQueue
|
||||||
|
{
|
||||||
|
return _bridge.uiManager.methodQueue;
|
||||||
|
}
|
||||||
|
|
||||||
- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName
|
- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName
|
||||||
{
|
{
|
||||||
if (count == 1) {
|
if (count == 1) {
|
||||||
|
|
|
@ -33,7 +33,9 @@ typedef struct {
|
||||||
CLLocationAccuracy accuracy;
|
CLLocationAccuracy accuracy;
|
||||||
} RCTLocationOptions;
|
} RCTLocationOptions;
|
||||||
|
|
||||||
static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
|
@implementation RCTConvert (RCTLocationOptions)
|
||||||
|
|
||||||
|
+ (RCTLocationOptions)RCTLocationOptions:(id)json
|
||||||
{
|
{
|
||||||
NSDictionary *options = [RCTConvert NSDictionary:json];
|
NSDictionary *options = [RCTConvert NSDictionary:json];
|
||||||
return (RCTLocationOptions){
|
return (RCTLocationOptions){
|
||||||
|
@ -43,6 +45,8 @@ static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */)
|
static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */)
|
||||||
{
|
{
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
|
@ -121,6 +125,7 @@ RCT_EXPORT_MODULE()
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[_locationManager stopUpdatingLocation];
|
[_locationManager stopUpdatingLocation];
|
||||||
|
_locationManager.delegate = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Private API
|
#pragma mark - Private API
|
||||||
|
@ -153,41 +158,33 @@ RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
#pragma mark - Public API
|
#pragma mark - Public API
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON)
|
RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options)
|
||||||
{
|
{
|
||||||
[self checkLocationConfig];
|
[self checkLocationConfig];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
// Select best options
|
||||||
|
_observerOptions = options;
|
||||||
|
for (RCTLocationRequest *request in _pendingRequests) {
|
||||||
|
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
||||||
|
}
|
||||||
|
|
||||||
// Select best options
|
_locationManager.desiredAccuracy = _observerOptions.accuracy;
|
||||||
_observerOptions = RCTLocationOptionsWithJSON(optionsJSON);
|
[self beginLocationUpdates];
|
||||||
for (RCTLocationRequest *request in _pendingRequests) {
|
_observingLocation = YES;
|
||||||
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
|
||||||
}
|
|
||||||
|
|
||||||
_locationManager.desiredAccuracy = _observerOptions.accuracy;
|
|
||||||
[self beginLocationUpdates];
|
|
||||||
_observingLocation = YES;
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(stopObserving)
|
RCT_EXPORT_METHOD(stopObserving)
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
// Stop observing
|
||||||
|
_observingLocation = NO;
|
||||||
|
|
||||||
// Stop observing
|
// Stop updating if no pending requests
|
||||||
_observingLocation = NO;
|
if (_pendingRequests.count == 0) {
|
||||||
|
[_locationManager stopUpdatingLocation];
|
||||||
// Stop updating if no pending requests
|
}
|
||||||
if (_pendingRequests.count == 0) {
|
|
||||||
[_locationManager stopUpdatingLocation];
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
|
RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||||
withSuccessCallback:(RCTResponseSenderBlock)successBlock
|
withSuccessCallback:(RCTResponseSenderBlock)successBlock
|
||||||
errorCallback:(RCTResponseSenderBlock)errorBlock)
|
errorCallback:(RCTResponseSenderBlock)errorBlock)
|
||||||
{
|
{
|
||||||
|
@ -198,56 +195,49 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
if (![CLLocationManager locationServicesEnabled]) {
|
||||||
|
if (errorBlock) {
|
||||||
if (![CLLocationManager locationServicesEnabled]) {
|
errorBlock(@[
|
||||||
if (errorBlock) {
|
RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
|
||||||
errorBlock(@[
|
]);
|
||||||
RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
|
|
||||||
]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
|
|
||||||
if (errorBlock) {
|
|
||||||
errorBlock(@[
|
|
||||||
RCTPositionError(RCTPositionErrorDenied, nil)
|
|
||||||
]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get options
|
|
||||||
RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON);
|
|
||||||
|
|
||||||
// Check if previous recorded location exists and is good enough
|
|
||||||
if (_lastLocationEvent &&
|
|
||||||
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
|
|
||||||
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
|
|
||||||
|
|
||||||
// Call success block with most recent known location
|
|
||||||
successBlock(@[_lastLocationEvent]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create request
|
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
|
||||||
RCTLocationRequest *request = [[RCTLocationRequest alloc] init];
|
if (errorBlock) {
|
||||||
request.successBlock = successBlock;
|
errorBlock(@[
|
||||||
request.errorBlock = errorBlock ?: ^(NSArray *args){};
|
RCTPositionError(RCTPositionErrorDenied, nil)
|
||||||
request.options = options;
|
]);
|
||||||
request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout
|
return;
|
||||||
target:self
|
}
|
||||||
selector:@selector(timeout:)
|
}
|
||||||
userInfo:request
|
|
||||||
repeats:NO];
|
|
||||||
[_pendingRequests addObject:request];
|
|
||||||
|
|
||||||
// Configure location manager and begin updating location
|
// Check if previous recorded location exists and is good enough
|
||||||
_locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
|
if (_lastLocationEvent &&
|
||||||
[self beginLocationUpdates];
|
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
|
||||||
|
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
|
||||||
|
|
||||||
});
|
// Call success block with most recent known location
|
||||||
|
successBlock(@[_lastLocationEvent]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
RCTLocationRequest *request = [[RCTLocationRequest alloc] init];
|
||||||
|
request.successBlock = successBlock;
|
||||||
|
request.errorBlock = errorBlock ?: ^(NSArray *args){};
|
||||||
|
request.options = options;
|
||||||
|
request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout
|
||||||
|
target:self
|
||||||
|
selector:@selector(timeout:)
|
||||||
|
userInfo:request
|
||||||
|
repeats:NO];
|
||||||
|
[_pendingRequests addObject:request];
|
||||||
|
|
||||||
|
// Configure location manager and begin updating location
|
||||||
|
_locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
|
||||||
|
[self beginLocationUpdates];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - CLLocationManagerDelegate
|
#pragma mark - CLLocationManagerDelegate
|
||||||
|
|
|
@ -62,8 +62,10 @@ RCT_EXPORT_METHOD(openURL:(NSURL *)URL)
|
||||||
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
|
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
|
||||||
callback:(RCTResponseSenderBlock)callback)
|
callback:(RCTResponseSenderBlock)callback)
|
||||||
{
|
{
|
||||||
BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL];
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
callback(@[@(canOpen)]);
|
BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL];
|
||||||
|
callback(@[@(canOpen)]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)constantsToExport
|
- (NSDictionary *)constantsToExport
|
||||||
|
|
|
@ -15,14 +15,24 @@
|
||||||
|
|
||||||
@interface RCTTestModule : NSObject <RCTBridgeModule>
|
@interface RCTTestModule : NSObject <RCTBridgeModule>
|
||||||
|
|
||||||
// This is typically polled while running the runloop until true
|
/**
|
||||||
@property (nonatomic, readonly, getter=isDone) BOOL done;
|
* The snapshot test controller for this module.
|
||||||
|
*/
|
||||||
// This is used to give meaningful names to snapshot image files.
|
@property (nonatomic, weak) FBSnapshotTestController *controller;
|
||||||
@property (nonatomic, assign) SEL testSelector;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the view to be snapshotted.
|
||||||
|
*/
|
||||||
@property (nonatomic, weak) UIView *view;
|
@property (nonatomic, weak) UIView *view;
|
||||||
|
|
||||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;
|
/**
|
||||||
|
* This is used to give meaningful names to snapshot image files.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign) SEL testSelector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is typically polled while running the runloop until true.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly, getter=isDone) BOOL done;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -12,21 +12,25 @@
|
||||||
#import "FBSnapshotTestController.h"
|
#import "FBSnapshotTestController.h"
|
||||||
#import "RCTAssert.h"
|
#import "RCTAssert.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
#import "RCTUIManager.h"
|
||||||
|
|
||||||
@implementation RCTTestModule
|
@implementation RCTTestModule
|
||||||
{
|
{
|
||||||
__weak FBSnapshotTestController *_snapshotController;
|
|
||||||
__weak UIView *_view;
|
|
||||||
NSMutableDictionary *_snapshotCounter;
|
NSMutableDictionary *_snapshotCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@synthesize bridge = _bridge;
|
||||||
|
|
||||||
RCT_EXPORT_MODULE()
|
RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view
|
- (dispatch_queue_t)methodQueue
|
||||||
|
{
|
||||||
|
return _bridge.uiManager.methodQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
{
|
{
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_snapshotController = controller;
|
|
||||||
_view = view;
|
|
||||||
_snapshotCounter = [NSMutableDictionary new];
|
_snapshotCounter = [NSMutableDictionary new];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
|
@ -34,30 +38,29 @@ RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
|
RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
|
||||||
{
|
{
|
||||||
if (!_snapshotController) {
|
RCTAssert(_controller != nil, @"No snapshot controller configured.");
|
||||||
RCTLogWarn(@"No snapshot controller configured.");
|
|
||||||
callback(@[]);
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
NSString *testName = NSStringFromSelector(_testSelector);
|
NSString *testName = NSStringFromSelector(_testSelector);
|
||||||
_snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1);
|
_snapshotCounter[testName] = [@([_snapshotCounter[testName] integerValue] + 1) stringValue];
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
BOOL success = [_snapshotController compareSnapshotOfView:_view
|
BOOL success = [_controller compareSnapshotOfView:_view
|
||||||
selector:_testSelector
|
selector:_testSelector
|
||||||
identifier:[_snapshotCounter[testName] stringValue]
|
identifier:_snapshotCounter[testName]
|
||||||
error:&error];
|
error:&error];
|
||||||
|
|
||||||
RCTAssert(success, @"Snapshot comparison failed: %@", error);
|
RCTAssert(success, @"Snapshot comparison failed: %@", error);
|
||||||
callback(@[]);
|
callback(@[]);
|
||||||
});
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(markTestCompleted)
|
RCT_EXPORT_METHOD(markTestCompleted)
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
_done = YES;
|
_done = YES;
|
||||||
});
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
@implementation RCTTestRunner
|
@implementation RCTTestRunner
|
||||||
{
|
{
|
||||||
FBSnapshotTestController *_snapshotController;
|
FBSnapshotTestController *_testController;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir
|
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir
|
||||||
|
@ -27,8 +27,8 @@
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
|
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
|
||||||
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
|
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
|
||||||
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
|
_testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
|
||||||
_snapshotController.referenceImagesDirectory = referenceDir;
|
_testController.referenceImagesDirectory = referenceDir;
|
||||||
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
|
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
|
@ -36,12 +36,12 @@
|
||||||
|
|
||||||
- (void)setRecordMode:(BOOL)recordMode
|
- (void)setRecordMode:(BOOL)recordMode
|
||||||
{
|
{
|
||||||
_snapshotController.recordMode = recordMode;
|
_testController.recordMode = recordMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)recordMode
|
- (BOOL)recordMode
|
||||||
{
|
{
|
||||||
return _snapshotController.recordMode;
|
return _testController.recordMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)runTest:(SEL)test module:(NSString *)moduleName
|
- (void)runTest:(SEL)test module:(NSString *)moduleName
|
||||||
|
@ -59,27 +59,22 @@
|
||||||
|
|
||||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||||
{
|
{
|
||||||
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:_scriptURL
|
||||||
if ([vc.view isKindOfClass:[RCTRootView class]]) {
|
moduleName:moduleName
|
||||||
[(RCTRootView *)vc.view invalidate]; // Make sure the normal app view doesn't interfere
|
launchOptions:nil];
|
||||||
}
|
|
||||||
vc.view = [[UIView alloc] init];
|
|
||||||
|
|
||||||
RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil];
|
|
||||||
testModule.testSelector = test;
|
|
||||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
|
|
||||||
moduleProvider:^(){
|
|
||||||
return @[testModule];
|
|
||||||
}
|
|
||||||
launchOptions:nil];
|
|
||||||
|
|
||||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
|
|
||||||
moduleName:moduleName];
|
|
||||||
testModule.view = rootView;
|
|
||||||
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
|
|
||||||
rootView.initialProperties = initialProps;
|
rootView.initialProperties = initialProps;
|
||||||
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
|
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
|
||||||
|
|
||||||
|
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
|
||||||
|
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
|
||||||
|
testModule.controller = _testController;
|
||||||
|
testModule.testSelector = test;
|
||||||
|
testModule.view = rootView;
|
||||||
|
|
||||||
|
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||||
|
vc.view = [[UIView alloc] init];
|
||||||
|
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
|
||||||
|
|
||||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||||
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||||
while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
|
while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
|
||||||
|
|
|
@ -101,13 +101,6 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, copy, readonly) NSDictionary *modules;
|
@property (nonatomic, copy, readonly) NSDictionary *modules;
|
||||||
|
|
||||||
/**
|
|
||||||
* The shadow queue is used to execute callbacks from the JavaScript code. All
|
|
||||||
* native hooks (e.g. exported module methods) will be executed on the shadow
|
|
||||||
* queue.
|
|
||||||
*/
|
|
||||||
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The launch options that were used to initialize the bridge.
|
* The launch options that were used to initialize the bridge.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -792,6 +792,7 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||||
@implementation RCTBridge
|
@implementation RCTBridge
|
||||||
{
|
{
|
||||||
RCTSparseArray *_modulesByID;
|
RCTSparseArray *_modulesByID;
|
||||||
|
RCTSparseArray *_queuesByID;
|
||||||
NSDictionary *_modulesByName;
|
NSDictionary *_modulesByName;
|
||||||
id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
||||||
Class _executorClass;
|
Class _executorClass;
|
||||||
|
@ -824,13 +825,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setUp
|
- (void)setUp
|
||||||
{
|
{
|
||||||
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
|
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
|
||||||
_javaScriptExecutor = [[executorClass alloc] init];
|
_javaScriptExecutor = [[executorClass alloc] init];
|
||||||
_latestJSExecutor = _javaScriptExecutor;
|
_latestJSExecutor = _javaScriptExecutor;
|
||||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
|
||||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
||||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||||
|
@ -848,21 +849,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
|
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
|
||||||
NSString *moduleName = RCTModuleNamesByID[moduleID];
|
NSString *moduleName = RCTModuleNamesByID[moduleID];
|
||||||
// Check if module instance has already been registered for this name
|
// Check if module instance has already been registered for this name
|
||||||
if ((_modulesByID[moduleID] = modulesByName[moduleName])) {
|
id<RCTBridgeModule> module = modulesByName[moduleName];
|
||||||
|
if (module) {
|
||||||
// Preregistered instances takes precedence, no questions asked
|
// Preregistered instances takes precedence, no questions asked
|
||||||
if (!preregisteredModules[moduleName]) {
|
if (!preregisteredModules[moduleName]) {
|
||||||
// It's OK to have a name collision as long as the second instance is nil
|
// It's OK to have a name collision as long as the second instance is nil
|
||||||
RCTAssert([[moduleClass alloc] init] == nil,
|
RCTAssert([[moduleClass alloc] init] == nil,
|
||||||
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \
|
@"Attempted to register RCTBridgeModule class %@ for the name "
|
||||||
but name was already registered by class %@", moduleClass,
|
"'%@', but name was already registered by class %@", moduleClass,
|
||||||
moduleName, [modulesByName[moduleName] class]);
|
moduleName, [modulesByName[moduleName] class]);
|
||||||
}
|
}
|
||||||
|
if ([module class] != moduleClass) {
|
||||||
|
RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
|
||||||
|
"in the project, but name was already registered by class %@."
|
||||||
|
"That's fine if it's intentional - just letting you know.",
|
||||||
|
moduleClass, moduleName, [modulesByName[moduleName] class]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Module name hasn't been used before, so go ahead and instantiate
|
// Module name hasn't been used before, so go ahead and instantiate
|
||||||
id<RCTBridgeModule> module = [[moduleClass alloc] init];
|
module = [[moduleClass alloc] init];
|
||||||
if (module) {
|
}
|
||||||
_modulesByID[moduleID] = modulesByName[moduleName] = module;
|
if (module) {
|
||||||
}
|
// Store module instance
|
||||||
|
_modulesByID[moduleID] = modulesByName[moduleName] = module;
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
@ -876,6 +885,14 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get method queue
|
||||||
|
_queuesByID = [[RCTSparseArray alloc] init];
|
||||||
|
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
|
||||||
|
if ([module respondsToSelector:@selector(methodQueue)]) {
|
||||||
|
_queuesByID[moduleID] = [module methodQueue] ?: dispatch_get_main_queue();
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
// Inject module data into JS context
|
// Inject module data into JS context
|
||||||
NSString *configJSON = RCTJSONStringify(@{
|
NSString *configJSON = RCTJSONStringify(@{
|
||||||
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
|
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
|
||||||
|
@ -1027,6 +1044,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
|
|
||||||
// Release modules (breaks retain cycle if module has strong bridge reference)
|
// Release modules (breaks retain cycle if module has strong bridge reference)
|
||||||
_modulesByID = nil;
|
_modulesByID = nil;
|
||||||
|
_queuesByID = nil;
|
||||||
_modulesByName = nil;
|
_modulesByName = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1203,6 +1221,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: if we sort the requests by module, we could dispatch once per
|
||||||
|
// module instead of per request, which would reduce the call overhead.
|
||||||
for (NSUInteger i = 0; i < numRequests; i++) {
|
for (NSUInteger i = 0; i < numRequests; i++) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
[self _handleRequestNumber:i
|
[self _handleRequestNumber:i
|
||||||
|
@ -1212,14 +1232,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: only used by RCTUIManager - can we eliminate this special case?
|
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
|
||||||
dispatch_async(self.shadowQueue, ^{
|
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
|
||||||
for (id module in _modulesByID.allObjects) {
|
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||||
|
dispatch_async(queue ?: dispatch_get_main_queue(), ^{
|
||||||
[module batchDidComplete];
|
[module batchDidComplete];
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)_handleRequestNumber:(NSUInteger)i
|
- (BOOL)_handleRequestNumber:(NSUInteger)i
|
||||||
|
@ -1241,7 +1262,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
RCTModuleMethod *method = methods[methodID];
|
RCTModuleMethod *method = methods[methodID];
|
||||||
|
|
||||||
__weak RCTBridge *weakSelf = self;
|
__weak RCTBridge *weakSelf = self;
|
||||||
dispatch_async(self.shadowQueue, ^{
|
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||||
|
dispatch_async(queue ?: dispatch_get_main_queue(), ^{
|
||||||
__strong RCTBridge *strongSelf = weakSelf;
|
__strong RCTBridge *strongSelf = weakSelf;
|
||||||
|
|
||||||
if (!strongSelf.isValid) {
|
if (!strongSelf.isValid) {
|
||||||
|
|
|
@ -89,6 +89,23 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||||
__attribute__((__aligned__(1))) \
|
__attribute__((__aligned__(1))) \
|
||||||
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
|
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The queue that will be used to call all exported methods. If omitted, this
|
||||||
|
* will default the main queue, which is recommended for any methods that
|
||||||
|
* interact with UIKit. If your methods perform heavy work such as filesystem
|
||||||
|
* or network access, you should return a custom serial queue. Example:
|
||||||
|
*
|
||||||
|
* - (dispatch_queue_t)methodQueue
|
||||||
|
* {
|
||||||
|
* return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Alternatively, if only some methods on the module should be executed on a
|
||||||
|
* background queue you can leave this method unimplemented, and simply
|
||||||
|
* dispatch_async() within the method itself.
|
||||||
|
*/
|
||||||
|
- (dispatch_queue_t)methodQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injects constants into JS. These constants are made accessible via
|
* Injects constants into JS. These constants are made accessible via
|
||||||
* NativeModules.ModuleName.X. This method is called when the module is
|
* NativeModules.ModuleName.X. This method is called when the module is
|
||||||
|
|
|
@ -138,7 +138,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
|
||||||
/**
|
/**
|
||||||
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
|
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
|
||||||
*/
|
*/
|
||||||
NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id);
|
NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
|
||||||
dispatch_once(&onceToken, ^{ \
|
dispatch_once(&onceToken, ^{ \
|
||||||
mapping = values; \
|
mapping = values; \
|
||||||
}); \
|
}); \
|
||||||
NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \
|
NSNumber *converted = RCTConvertEnumValue(#type, mapping, @(default), json); \
|
||||||
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
|
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,10 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||||
|
|
||||||
+ (NSURL *)NSURL:(id)json
|
+ (NSURL *)NSURL:(id)json
|
||||||
{
|
{
|
||||||
|
if (!json || json == (id)kCFNull) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
if (![json isKindOfClass:[NSString class]]) {
|
if (![json isKindOfClass:[NSString class]]) {
|
||||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
||||||
return nil;
|
return nil;
|
||||||
|
@ -115,7 +119,7 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0
|
||||||
// JS standard for time zones is minutes.
|
// JS standard for time zones is minutes.
|
||||||
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
|
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
|
||||||
|
|
||||||
NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
|
NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
|
||||||
{
|
{
|
||||||
if (!json || json == (id)kCFNull) {
|
if (!json || json == (id)kCFNull) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
|
@ -64,37 +64,34 @@ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
|
||||||
|
message:message
|
||||||
|
delegate:self
|
||||||
|
cancelButtonTitle:nil
|
||||||
|
otherButtonTitles:nil];
|
||||||
|
|
||||||
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
|
NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count];
|
||||||
message:message
|
|
||||||
delegate:self
|
|
||||||
cancelButtonTitle:nil
|
|
||||||
otherButtonTitles:nil];
|
|
||||||
|
|
||||||
NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count];
|
NSInteger index = 0;
|
||||||
|
for (NSDictionary *button in buttons) {
|
||||||
NSInteger index = 0;
|
if (button.count != 1) {
|
||||||
for (NSDictionary *button in buttons) {
|
RCTLogError(@"Button definitions should have exactly one key.");
|
||||||
if (button.count != 1) {
|
|
||||||
RCTLogError(@"Button definitions should have exactly one key.");
|
|
||||||
}
|
|
||||||
NSString *buttonKey = [button.allKeys firstObject];
|
|
||||||
NSString *buttonTitle = [button[buttonKey] description];
|
|
||||||
[alertView addButtonWithTitle:buttonTitle];
|
|
||||||
if ([buttonKey isEqualToString: @"cancel"]) {
|
|
||||||
alertView.cancelButtonIndex = index;
|
|
||||||
}
|
|
||||||
[buttonKeys addObject:buttonKey];
|
|
||||||
index ++;
|
|
||||||
}
|
}
|
||||||
|
NSString *buttonKey = [button.allKeys firstObject];
|
||||||
|
NSString *buttonTitle = [button[buttonKey] description];
|
||||||
|
[alertView addButtonWithTitle:buttonTitle];
|
||||||
|
if ([buttonKey isEqualToString: @"cancel"]) {
|
||||||
|
alertView.cancelButtonIndex = index;
|
||||||
|
}
|
||||||
|
[buttonKeys addObject:buttonKey];
|
||||||
|
index ++;
|
||||||
|
}
|
||||||
|
|
||||||
[_alerts addObject:alertView];
|
[_alerts addObject:alertView];
|
||||||
[_alertCallbacks addObject:callback ?: ^(id unused) {}];
|
[_alertCallbacks addObject:callback ?: ^(id unused) {}];
|
||||||
[_alertButtonKeys addObject:buttonKeys];
|
[_alertButtonKeys addObject:buttonKeys];
|
||||||
|
|
||||||
[alertView show];
|
[alertView show];
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UIAlertViewDelegate
|
#pragma mark - UIAlertViewDelegate
|
||||||
|
|
|
@ -61,20 +61,6 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dispatch_queue_t RCTFileQueue(void)
|
|
||||||
{
|
|
||||||
static dispatch_queue_t fileQueue = NULL;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
// All JS is single threaded, so a serial queue is our only option.
|
|
||||||
fileQueue = dispatch_queue_create("com.facebook.rkFile", DISPATCH_QUEUE_SERIAL);
|
|
||||||
dispatch_set_target_queue(fileQueue,
|
|
||||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
return fileQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - RCTAsyncLocalStorage
|
#pragma mark - RCTAsyncLocalStorage
|
||||||
|
|
||||||
@implementation RCTAsyncLocalStorage
|
@implementation RCTAsyncLocalStorage
|
||||||
|
@ -90,6 +76,11 @@ static dispatch_queue_t RCTFileQueue(void)
|
||||||
|
|
||||||
RCT_EXPORT_MODULE()
|
RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
|
- (dispatch_queue_t)methodQueue
|
||||||
|
{
|
||||||
|
return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *)_filePathForKey:(NSString *)key
|
- (NSString *)_filePathForKey:(NSString *)key
|
||||||
{
|
{
|
||||||
NSString *safeFileName = RCTMD5Hash(key);
|
NSString *safeFileName = RCTMD5Hash(key);
|
||||||
|
@ -196,99 +187,89 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_async(RCTFileQueue(), ^{
|
id errorOut = [self _ensureSetup];
|
||||||
id errorOut = [self _ensureSetup];
|
if (errorOut) {
|
||||||
if (errorOut) {
|
callback(@[@[errorOut], [NSNull null]]);
|
||||||
callback(@[@[errorOut], [NSNull null]]);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
NSMutableArray *errors;
|
||||||
NSMutableArray *errors;
|
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
|
||||||
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
|
for (NSString *key in keys) {
|
||||||
for (NSString *key in keys) {
|
id keyError = [self _appendItemForKey:key toArray:result];
|
||||||
id keyError = [self _appendItemForKey:key toArray:result];
|
RCTAppendError(keyError, &errors);
|
||||||
RCTAppendError(keyError, &errors);
|
}
|
||||||
}
|
[self _writeManifest:&errors];
|
||||||
[self _writeManifest:&errors];
|
callback(@[errors ?: [NSNull null], result]);
|
||||||
callback(@[errors ?: [NSNull null], result]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs
|
RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs
|
||||||
callback:(RCTResponseSenderBlock)callback)
|
callback:(RCTResponseSenderBlock)callback)
|
||||||
{
|
{
|
||||||
dispatch_async(RCTFileQueue(), ^{
|
id errorOut = [self _ensureSetup];
|
||||||
id errorOut = [self _ensureSetup];
|
if (errorOut) {
|
||||||
if (errorOut) {
|
callback(@[@[errorOut]]);
|
||||||
callback(@[@[errorOut]]);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
NSMutableArray *errors;
|
||||||
NSMutableArray *errors;
|
for (NSArray *entry in kvPairs) {
|
||||||
for (NSArray *entry in kvPairs) {
|
id keyError = [self _writeEntry:entry];
|
||||||
id keyError = [self _writeEntry:entry];
|
RCTAppendError(keyError, &errors);
|
||||||
RCTAppendError(keyError, &errors);
|
}
|
||||||
}
|
[self _writeManifest:&errors];
|
||||||
[self _writeManifest:&errors];
|
if (callback) {
|
||||||
if (callback) {
|
callback(@[errors ?: [NSNull null]]);
|
||||||
callback(@[errors ?: [NSNull null]]);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
|
RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
|
||||||
callback:(RCTResponseSenderBlock)callback)
|
callback:(RCTResponseSenderBlock)callback)
|
||||||
{
|
{
|
||||||
dispatch_async(RCTFileQueue(), ^{
|
id errorOut = [self _ensureSetup];
|
||||||
id errorOut = [self _ensureSetup];
|
if (errorOut) {
|
||||||
if (errorOut) {
|
callback(@[@[errorOut]]);
|
||||||
callback(@[@[errorOut]]);
|
return;
|
||||||
return;
|
}
|
||||||
|
NSMutableArray *errors;
|
||||||
|
for (NSString *key in keys) {
|
||||||
|
id keyError = RCTErrorForKey(key);
|
||||||
|
if (!keyError) {
|
||||||
|
NSString *filePath = [self _filePathForKey:key];
|
||||||
|
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
||||||
|
[_manifest removeObjectForKey:key];
|
||||||
}
|
}
|
||||||
NSMutableArray *errors;
|
RCTAppendError(keyError, &errors);
|
||||||
for (NSString *key in keys) {
|
}
|
||||||
id keyError = RCTErrorForKey(key);
|
[self _writeManifest:&errors];
|
||||||
if (!keyError) {
|
if (callback) {
|
||||||
NSString *filePath = [self _filePathForKey:key];
|
callback(@[errors ?: [NSNull null]]);
|
||||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
}
|
||||||
[_manifest removeObjectForKey:key];
|
|
||||||
}
|
|
||||||
RCTAppendError(keyError, &errors);
|
|
||||||
}
|
|
||||||
[self _writeManifest:&errors];
|
|
||||||
if (callback) {
|
|
||||||
callback(@[errors ?: [NSNull null]]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback)
|
RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback)
|
||||||
{
|
{
|
||||||
dispatch_async(RCTFileQueue(), ^{
|
id errorOut = [self _ensureSetup];
|
||||||
id errorOut = [self _ensureSetup];
|
if (!errorOut) {
|
||||||
if (!errorOut) {
|
NSError *error;
|
||||||
NSError *error;
|
for (NSString *key in _manifest) {
|
||||||
for (NSString *key in _manifest) {
|
NSString *filePath = [self _filePathForKey:key];
|
||||||
NSString *filePath = [self _filePathForKey:key];
|
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
||||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
|
||||||
}
|
|
||||||
[_manifest removeAllObjects];
|
|
||||||
errorOut = [self _writeManifest:nil];
|
|
||||||
}
|
}
|
||||||
if (callback) {
|
[_manifest removeAllObjects];
|
||||||
callback(@[errorOut ?: [NSNull null]]);
|
errorOut = [self _writeManifest:nil];
|
||||||
}
|
}
|
||||||
});
|
if (callback) {
|
||||||
|
callback(@[errorOut ?: [NSNull null]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback)
|
RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback)
|
||||||
{
|
{
|
||||||
dispatch_async(RCTFileQueue(), ^{
|
id errorOut = [self _ensureSetup];
|
||||||
id errorOut = [self _ensureSetup];
|
if (errorOut) {
|
||||||
if (errorOut) {
|
callback(@[errorOut, [NSNull null]]);
|
||||||
callback(@[errorOut, [NSNull null]]);
|
} else {
|
||||||
} else {
|
callback(@[[NSNull null], [_manifest allKeys]]);
|
||||||
callback(@[[NSNull null], [_manifest allKeys]]);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -48,13 +48,11 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||||
#else
|
#else
|
||||||
if (RCTReloadRetries < _maxReloadAttempts) {
|
if (RCTReloadRetries < _maxReloadAttempts) {
|
||||||
RCTReloadRetries++;
|
RCTReloadRetries++;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
NSError *error;
|
NSError *error;
|
||||||
const NSUInteger MAX_SANITIZED_LENGTH = 75;
|
const NSUInteger MAX_SANITIZED_LENGTH = 75;
|
||||||
|
|
|
@ -24,7 +24,6 @@ RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
|
||||||
} else {
|
} else {
|
||||||
failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]);
|
failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -29,31 +29,25 @@ RCT_EXPORT_MODULE()
|
||||||
RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle
|
RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle
|
||||||
animated:(BOOL)animated)
|
animated:(BOOL)animated)
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
if (RCTViewControllerBasedStatusBarAppearance()) {
|
||||||
|
RCTLogError(@"RCTStatusBarManager module requires that the \
|
||||||
if (RCTViewControllerBasedStatusBarAppearance()) {
|
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
|
||||||
RCTLogError(@"RCTStatusBarManager module requires that the \
|
} else {
|
||||||
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
|
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle
|
||||||
} else {
|
animated:animated];
|
||||||
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle
|
}
|
||||||
animated:animated];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(setHidden:(BOOL)hidden
|
RCT_EXPORT_METHOD(setHidden:(BOOL)hidden
|
||||||
withAnimation:(UIStatusBarAnimation)animation)
|
withAnimation:(UIStatusBarAnimation)animation)
|
||||||
{
|
{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
if (RCTViewControllerBasedStatusBarAppearance()) {
|
||||||
|
RCTLogError(@"RCTStatusBarManager module requires that the \
|
||||||
if (RCTViewControllerBasedStatusBarAppearance()) {
|
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
|
||||||
RCTLogError(@"RCTStatusBarManager module requires that the \
|
} else {
|
||||||
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
|
[[UIApplication sharedApplication] setStatusBarHidden:hidden
|
||||||
} else {
|
withAnimation:animation];
|
||||||
[[UIApplication sharedApplication] setStatusBarHidden:hidden
|
}
|
||||||
withAnimation:animation];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)constantsToExport
|
- (NSDictionary *)constantsToExport
|
||||||
|
|
|
@ -187,21 +187,17 @@ RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID
|
||||||
interval:jsDuration
|
interval:jsDuration
|
||||||
targetTime:targetTime
|
targetTime:targetTime
|
||||||
repeats:repeats];
|
repeats:repeats];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
_timers[callbackID] = timer;
|
||||||
_timers[callbackID] = timer;
|
[self startTimers];
|
||||||
[self startTimers];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(deleteTimer:(NSNumber *)timerID)
|
RCT_EXPORT_METHOD(deleteTimer:(NSNumber *)timerID)
|
||||||
{
|
{
|
||||||
if (timerID) {
|
if (timerID) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
_timers[timerID] = nil;
|
||||||
_timers[timerID] = nil;
|
if (_timers.count == 0) {
|
||||||
if (_timers.count == 0) {
|
[self stopTimers];
|
||||||
[self stopTimers];
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
RCTLogWarn(@"Called deleteTimer: with a nil timerID");
|
RCTLogWarn(@"Called deleteTimer: with a nil timerID");
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,7 +178,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
|
||||||
|
|
||||||
@implementation RCTUIManager
|
@implementation RCTUIManager
|
||||||
{
|
{
|
||||||
__weak dispatch_queue_t _shadowQueue;
|
dispatch_queue_t _shadowQueue;
|
||||||
|
|
||||||
// Root views are only mutated on the shadow queue
|
// Root views are only mutated on the shadow queue
|
||||||
NSMutableSet *_rootViewTags;
|
NSMutableSet *_rootViewTags;
|
||||||
|
@ -242,24 +242,12 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This private constructor should only be called when creating
|
|
||||||
* isolated UIImanager instances for testing. Normal initialization
|
|
||||||
* is via -init:, which is called automatically by the bridge.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue
|
|
||||||
{
|
|
||||||
if ((self = [self init])) {
|
|
||||||
_shadowQueue = shadowQueue;
|
|
||||||
_viewManagers = [[NSMutableDictionary alloc] init];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
{
|
{
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
|
|
||||||
|
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
|
||||||
_pendingUIBlocksLock = [[NSLock alloc] init];
|
_pendingUIBlocksLock = [[NSLock alloc] init];
|
||||||
|
|
||||||
_defaultShadowViews = [[NSMutableDictionary alloc] init];
|
_defaultShadowViews = [[NSMutableDictionary alloc] init];
|
||||||
|
@ -310,7 +298,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
||||||
RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance");
|
RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance");
|
||||||
|
|
||||||
_bridge = bridge;
|
_bridge = bridge;
|
||||||
_shadowQueue = _bridge.shadowQueue;
|
|
||||||
_shadowViewRegistry = [[RCTSparseArray alloc] init];
|
_shadowViewRegistry = [[RCTSparseArray alloc] init];
|
||||||
|
|
||||||
// Get view managers from bridge
|
// Get view managers from bridge
|
||||||
|
@ -328,6 +315,11 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
||||||
_viewConfigs = [viewConfigs copy];
|
_viewConfigs = [viewConfigs copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (dispatch_queue_t)methodQueue
|
||||||
|
{
|
||||||
|
return _shadowQueue;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)registerRootView:(UIView *)rootView;
|
- (void)registerRootView:(UIView *)rootView;
|
||||||
{
|
{
|
||||||
RCTAssertMainThread();
|
RCTAssertMainThread();
|
||||||
|
@ -366,7 +358,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
||||||
NSNumber *reactTag = rootView.reactTag;
|
NSNumber *reactTag = rootView.reactTag;
|
||||||
RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
|
RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
|
||||||
|
|
||||||
dispatch_async(_bridge.shadowQueue, ^{
|
dispatch_async(_shadowQueue, ^{
|
||||||
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
|
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
|
||||||
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
|
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
|
||||||
rootShadowView.frame = frame;
|
rootShadowView.frame = frame;
|
||||||
|
@ -396,8 +388,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
||||||
|
|
||||||
- (void)addUIBlock:(RCTViewManagerUIBlock)block
|
- (void)addUIBlock:(RCTViewManagerUIBlock)block
|
||||||
{
|
{
|
||||||
RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread");
|
|
||||||
|
|
||||||
if (!self.isValid) {
|
if (!self.isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -417,7 +407,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
|
||||||
|
|
||||||
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView
|
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView
|
||||||
{
|
{
|
||||||
RCTAssert(![NSThread isMainThread], @"This should never be executed on main thread.");
|
RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread");
|
||||||
|
|
||||||
NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1];
|
NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1];
|
||||||
|
|
||||||
|
@ -679,6 +669,8 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
||||||
|
|
||||||
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
||||||
|
|
||||||
|
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
|
||||||
|
|
||||||
// Figure out what to insert - merge temporary inserts and adds
|
// Figure out what to insert - merge temporary inserts and adds
|
||||||
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
|
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
|
||||||
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
||||||
|
@ -886,8 +878,6 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag)
|
||||||
|
|
||||||
- (void)flushUIBlocks
|
- (void)flushUIBlocks
|
||||||
{
|
{
|
||||||
RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread");
|
|
||||||
|
|
||||||
// First copy the previous blocks into a temporary variable, then reset the
|
// First copy the previous blocks into a temporary variable, then reset the
|
||||||
// pending blocks to a new array. This guards against mutation while
|
// pending blocks to a new array. This guards against mutation while
|
||||||
// processing the pending blocks in another thread.
|
// processing the pending blocks in another thread.
|
||||||
|
|
|
@ -109,8 +109,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
|
||||||
* within the view or shadowView.
|
* within the view or shadowView.
|
||||||
*/
|
*/
|
||||||
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
|
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
|
||||||
RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
|
RCT_CUSTOM_VIEW_PROPERTY(name, type, UIView) { \
|
||||||
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
|
|
||||||
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
|
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
|
||||||
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
|
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
|
||||||
RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \
|
RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \
|
||||||
|
@ -118,8 +117,7 @@ RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \
|
#define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \
|
||||||
RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
|
RCT_CUSTOM_SHADOW_PROPERTY(name, type, RCTShadowView) { \
|
||||||
- (void)set_##name:(id)json forShadowView:(id)view withDefaultView:(id)defaultView { \
|
|
||||||
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
|
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
|
||||||
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
|
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
|
||||||
RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \
|
RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \
|
||||||
|
@ -132,11 +130,11 @@ RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
|
||||||
* refer to "json", "view" and "defaultView" to implement the required logic.
|
* refer to "json", "view" and "defaultView" to implement the required logic.
|
||||||
*/
|
*/
|
||||||
#define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \
|
#define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \
|
||||||
RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
|
+ (NSDictionary *)getPropConfigView_##name { return @{@"name": @#name, @"type": @#type}; } \
|
||||||
- (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView
|
- (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView
|
||||||
|
|
||||||
#define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \
|
#define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \
|
||||||
RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
|
+ (NSDictionary *)getPropConfigShadow_##name { return @{@"name": @#name, @"type": @#type}; } \
|
||||||
- (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView
|
- (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,17 +162,4 @@ RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
|
||||||
[self set_##newName:json forView:view withDefaultView:defaultView]; \
|
[self set_##newName:json forView:view withDefaultView:defaultView]; \
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PROP_CONFIG macros should only be paired with property setters.
|
|
||||||
*/
|
|
||||||
#define RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
|
|
||||||
+ (NSDictionary *)getPropConfigView_##name { \
|
|
||||||
return @{@"name": @#name, @"type": @#type}; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
|
|
||||||
+ (NSDictionary *)getPropConfigShadow_##name { \
|
|
||||||
return @{@"name": @#name, @"type": @#type}; \
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#import "RCTEventDispatcher.h"
|
#import "RCTEventDispatcher.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
#import "RCTShadowView.h"
|
#import "RCTShadowView.h"
|
||||||
|
#import "RCTUIManager.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
#import "RCTView.h"
|
#import "RCTView.h"
|
||||||
|
|
||||||
|
@ -23,6 +24,11 @@
|
||||||
|
|
||||||
RCT_EXPORT_MODULE()
|
RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
|
- (dispatch_queue_t)methodQueue
|
||||||
|
{
|
||||||
|
return [_bridge.uiManager methodQueue];
|
||||||
|
}
|
||||||
|
|
||||||
- (UIView *)view
|
- (UIView *)view
|
||||||
{
|
{
|
||||||
return [[RCTView alloc] init];
|
return [[RCTView alloc] init];
|
||||||
|
|
Loading…
Reference in New Issue