2015-02-02 updates

- Removed special-case treatment of RCTTiming and RCTUIManager modules | Nick Lockwood
This commit is contained in:
Christopher Chedeau 2015-02-03 16:12:07 -08:00
parent 00f0ebccdf
commit ccd8f184af
11 changed files with 340 additions and 360 deletions

View File

@ -92,27 +92,27 @@ the responder system.
# FAQ # FAQ
Q. How does debugging work? Can I set breakpoints in my JS? Q. How does debugging work? Can I set breakpoints in my JS?
A. We are going to add the ability to use the Chrome developer tools soon. We A. We are going to add the ability to use the Chrome developer tools soon. We
are very passionate about building the best possible developer experience. are very passionate about building the best possible developer experience.
Q. When is this coming to Android/Windows/OS X/etc? Q. When is this coming to Android/Windows/OS X/etc?
A. We're working on Android, and we are excited to release it as soon as we can. A. We're working on Android, and we are excited to release it as soon as we can.
We are looking forward to the community helping us target other platforms as We are looking forward to the community helping us target other platforms as
well :) well :)
Q. How do I create my own app? Q. How do I create my own app?
A. Copy the entire `Examples/TicTacToe` folder, rename stuff in Xcode, and A. Copy the entire `Examples/TicTacToe` folder, rename stuff in Xcode, and
replace the `TicTacToeApp.js` with your own. Then, in `AppDelegate.m`, update replace the `TicTacToeApp.js` with your own. Then, in `AppDelegate.m`, update
`moduleName` to match your call to `moduleName` to match your call to
`Bundler.registerComponent(<moduleName>, <componentName>)` at the bottom of your `Bundler.registerComponent(<moduleName>, <componentName>)` at the bottom of your
JS file, and update `jsCodeLocation` to match your JS file name and location. JS file, and update `jsCodeLocation` to match your JS file name and location.
Q. Can I submit my own React Native app to the App Store? Q. Can I submit my own React Native app to the App Store?
A. Not yet, but you will be able to soon. If you build something you want to A. Not yet, but you will be able to soon. If you build something you want to
submit to the App Store, come talk to us ASAP. submit to the App Store, come talk to us ASAP.
Q. How do I deploy to my device? Q. How do I deploy to my device?
A. You can change `localhost` in `AppDelegate.m` to your laptop's IP address and A. You can change `localhost` in `AppDelegate.m` to your laptop's IP address and
grab the bundle over the same Wi-Fi network. You can also download the bundle grab the bundle over the same Wi-Fi network. You can also download the bundle
that the React packager generates, save it to the file `main.jsbundle`, and add it that the React packager generates, save it to the file `main.jsbundle`, and add it
@ -120,20 +120,20 @@ as a static resource in your Xcode project. Then set the `jsCodeLocation` in
`AppDelegate.m` to point to that file and deploy to your device like you would `AppDelegate.m` to point to that file and deploy to your device like you would
any other app. any other app.
Q. What's up with this private repo? Why aren't you just open sourcing it now? Q. What's up with this private repo? Why aren't you just open sourcing it now?
A. We want input from the React community before we open the floodgates so we A. We want input from the React community before we open the floodgates so we
can incorporate your feedback, and we also have a bunch more features we want to can incorporate your feedback, and we also have a bunch more features we want to
add to make a more complete offering before we open source. add to make a more complete offering before we open source.
Q. Do you have to ship a JS runtime with your apps? Q. Do you have to ship a JS runtime with your apps?
A. No, we just use the JavaScriptCore public API that is part of iOS 7 and A. No, we just use the JavaScriptCore public API that is part of iOS 7 and
later. later.
Q. How do I add more native capabilities? Q. How do I add more native capabilities?
A. React Native is designed to be extensible - come talk to us, we would love to A. React Native is designed to be extensible - come talk to us, we would love to
work with you. work with you.
Q. Can I reuse existing iOS code? Q. Can I reuse existing iOS code?
A. Yes, React Native is designed to be extensible and allow integration of all A. Yes, React Native is designed to be extensible and allow integration of all
sorts of native components, such as `UINavigationController` (available as sorts of native components, such as `UINavigationController` (available as
`<NavigatorIOS>`), `MKMapView` (not available yet), or your own custom `<NavigatorIOS>`), `MKMapView` (not available yet), or your own custom

View File

@ -6,33 +6,8 @@
@protocol RCTNativeModule; @protocol RCTNativeModule;
@class RCTUIManager;
@class RCTEventDispatcher; @class RCTEventDispatcher;
@class RCTRootView;
/**
* Functions are the one thing that aren't automatically converted to OBJC
* blocks, according to this revert: http://trac.webkit.org/changeset/144489
* They must be expressed as `JSValue`s.
*
* But storing callbacks causes reference cycles!
* http://stackoverflow.com/questions/19202248/how-can-i-use-jsmanagedvalue-to-avoid-a-reference-cycle-without-the-jsvalue-gett
* We'll live with the leak for now, but need to clean this up asap:
* Passing a reference to the `context` to the bridge would make it easy to
* execute JS. We can add `JSManagedValue`s to protect against this. The same
* needs to be done in `RCTTiming` and friends.
*/
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
RCTBridgeFieldResponseCBIDs,
RCTBridgeFieldResponseReturnValues,
RCTBridgeFieldFlushDateMillis
};
/** /**
* Utilities for constructing common response objects. When sending a * Utilities for constructing common response objects. When sending a
@ -59,19 +34,19 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
@interface RCTBridge : NSObject <RCTInvalidating> @interface RCTBridge : NSObject <RCTInvalidating>
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
shadowQueue:(dispatch_queue_t)shadowQueue
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig; javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig;
- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args; - (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args;
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete; - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
- (void)enqueueUpdateTimers;
@property (nonatomic, readonly) RCTUIManager *uiManager;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher; @property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
// For use in implementing delegates, which may need to queue responses. // For use in implementing delegates, which may need to queue responses.
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID; - (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID;
- (void)registerRootView:(RCTRootView *)rootView;
/** /**
* Global logging function will print to both xcode and js debugger consoles. * Global logging function will print to both xcode and js debugger consoles.
* *

View File

@ -9,11 +9,34 @@
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTModuleIDs.h" #import "RCTModuleIDs.h"
#import "RCTTiming.h"
#import "RCTUIManager.h"
#import "RCTUtils.h" #import "RCTUtils.h"
NSString *RCTModuleName(Class moduleClass) /**
* Functions are the one thing that aren't automatically converted to OBJC
* blocks, according to this revert: http://trac.webkit.org/changeset/144489
* They must be expressed as `JSValue`s.
*
* But storing callbacks causes reference cycles!
* http://stackoverflow.com/questions/19202248/how-can-i-use-jsmanagedvalue-to-avoid-a-reference-cycle-without-the-jsvalue-gett
* We'll live with the leak for now, but need to clean this up asap:
* Passing a reference to the `context` to the bridge would make it easy to
* execute JS. We can add `JSManagedValue`s to protect against this. The same
* needs to be done in `RCTTiming` and friends.
*/
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
RCTBridgeFieldResponseCBIDs,
RCTBridgeFieldResponseReturnValues,
RCTBridgeFieldFlushDateMillis
};
static NSString *RCTModuleName(Class moduleClass)
{ {
if ([moduleClass respondsToSelector:@selector(moduleName)]) { if ([moduleClass respondsToSelector:@selector(moduleName)]) {
@ -22,22 +45,11 @@ NSString *RCTModuleName(Class moduleClass)
} else { } else {
// Default implementation, works in most cases // Default implementation, works in most cases
NSString *className = NSStringFromClass(moduleClass); return NSStringFromClass(moduleClass);
// TODO: be more consistent with naming so that this check isn't needed
if ([moduleClass conformsToProtocol:@protocol(RCTNativeViewModule)]) {
if ([className hasPrefix:@"RCTUI"]) {
className = [className substringFromIndex:@"RCT".length];
}
if ([className hasSuffix:@"Manager"]) {
className = [className substringToIndex:className.length - @"Manager".length];
}
}
return className;
} }
} }
NSDictionary *RCTNativeModuleClasses(void) static NSDictionary *RCTNativeModuleClasses(void)
{ {
static NSMutableDictionary *modules; static NSMutableDictionary *modules;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
@ -79,46 +91,30 @@ NSDictionary *RCTNativeModuleClasses(void)
{ {
NSMutableDictionary *_moduleInstances; NSMutableDictionary *_moduleInstances;
NSDictionary *_javaScriptModulesConfig; NSDictionary *_javaScriptModulesConfig;
dispatch_queue_t _shadowQueue;
RCTTiming *_timing;
id<RCTJavaScriptExecutor> _javaScriptExecutor; id<RCTJavaScriptExecutor> _javaScriptExecutor;
} }
static id<RCTJavaScriptExecutor> _latestJSExecutor; static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
shadowQueue:(dispatch_queue_t)shadowQueue
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig
{ {
if ((self = [super init])) { if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor; _javaScriptExecutor = javaScriptExecutor;
_latestJSExecutor = _javaScriptExecutor; _latestJSExecutor = _javaScriptExecutor;
_shadowQueue = shadowQueue;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_moduleInstances = [[NSMutableDictionary alloc] init];
// TODO (#5906496): Remove special case
_timing = [[RCTTiming alloc] initWithBridge:self];
_javaScriptModulesConfig = javaScriptModulesConfig; _javaScriptModulesConfig = javaScriptModulesConfig;
_moduleInstances[RCTModuleName([RCTTiming class])] = _timing; _shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// TODO (#5906496): Remove special case // Register modules
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init]; _moduleInstances = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if ([moduleClass conformsToProtocol:@protocol(RCTNativeViewModule)]) {
viewManagers[moduleName] = [[moduleClass alloc] init];
}
}];
_uiManager = [[RCTUIManager alloc] initWithShadowQueue:_shadowQueue viewManagers:viewManagers];
_uiManager.eventDispatcher = _eventDispatcher;
_moduleInstances[RCTModuleName([RCTUIManager class])] = _uiManager;
[_moduleInstances addEntriesFromDictionary:viewManagers];
// Register remaining modules
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) { [RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) { if (_moduleInstances[moduleName] == nil) {
_moduleInstances[moduleName] = [[moduleClass alloc] init]; if ([moduleClass instancesRespondToSelector:@selector(initWithBridge:)]) {
_moduleInstances[moduleName] = [[moduleClass alloc] initWithBridge:self];
} else {
_moduleInstances[moduleName] = [[moduleClass alloc] init];
}
} }
}]; }];
@ -148,18 +144,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_javaScriptExecutor = nil; _javaScriptExecutor = nil;
dispatch_sync(_shadowQueue, ^{ dispatch_sync(_shadowQueue, ^{
// Make sure all dispatchers have been executed before // Make sure all dispatchers have been executed before continuing
// freeing up memory from _asyncHookMapByModuleID // TODO: is this still needed?
}); });
for (id target in _moduleInstances.objectEnumerator) { for (id target in _moduleInstances.objectEnumerator) {
if ([target respondsToSelector:@selector(invalidate)]) { if ([target respondsToSelector:@selector(invalidate)]) {
[(id<RCTInvalidating>)target invalidate]; [(id<RCTInvalidating>)target invalidate];
} }
} }
[_moduleInstances removeAllObjects]; [_moduleInstances removeAllObjects];
_timing = nil;
} }
/** /**
@ -199,11 +193,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}]; }];
} }
- (void)enqueueUpdateTimers
{
[_timing enqueueUpdateTimers];
}
#pragma mark - Payload Generation #pragma mark - Payload Generation
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
@ -274,12 +263,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
} }
} }
NSArray *moduleIDs = [requestsArray objectAtIndex:RCTBridgeFieldRequestModuleIDs]; NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
NSArray *methodIDs = [requestsArray objectAtIndex:RCTBridgeFieldMethodIDs]; NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
NSArray *paramss = [requestsArray objectAtIndex:RCTBridgeFieldParamss]; NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
NSUInteger numRequests = [moduleIDs count]; NSUInteger numRequests = [moduleIDs count];
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramss count]; BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
if (!allSame) { if (!allSame) {
RCTLogMustFix(@"Invalid data message - all must be length: %zd", numRequests); RCTLogMustFix(@"Invalid data message - all must be length: %zd", numRequests);
return; return;
@ -288,31 +277,102 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
for (NSUInteger i = 0; i < numRequests; i++) { for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool { @autoreleasepool {
[self _handleRequestNumber:i [self _handleRequestNumber:i
moduleID:[moduleIDs objectAtIndex:i] moduleID:[moduleIDs[i] integerValue]
methodID:[methodIDs objectAtIndex:i] methodID:[methodIDs[i] integerValue]
params:[paramss objectAtIndex:i]]; params:paramsArrays[i]];
} }
} }
// Update modules // TODO: only used by RCTUIManager - can we eliminate this special case?
for (id target in _moduleInstances.objectEnumerator) { dispatch_async(_shadowQueue, ^{
if ([target respondsToSelector:@selector(batchDidComplete)]) { for (id target in _moduleInstances.objectEnumerator) {
dispatch_async(_shadowQueue, ^{ if ([target respondsToSelector:@selector(batchDidComplete)]) {
[target batchDidComplete]; [target batchDidComplete];
}); }
} }
} });
} }
- (void)_handleRequestNumber:(NSUInteger)i moduleID:(id)moduleID methodID:(id)methodID params:(id)params - (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSInteger)moduleID
methodID:(NSInteger)methodID
params:(NSArray *)params
{ {
if (![moduleID isKindOfClass:[NSNumber class]] || ![methodID isKindOfClass:[NSNumber class]] || ![params isKindOfClass:[NSArray class]]) { if (![params isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i); RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i);
return; return NO;
} }
[self _dispatchUsingAsyncHookMapWithModuleID:[moduleID integerValue]
methodID:[methodID integerValue] if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
params:params]; return NO;
}
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(moduleID);
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
if (methodID < 0 || methodID >= methods.count) {
return NO;
}
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogMustFix(@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName);
return NO;
}
__weak RCTBridge *weakSelf = self;
dispatch_async(_shadowQueue, ^{
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}
NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
// TODO: we should just store module instances by index, since that's how we look them up anyway
id target = strongSelf->_moduleInstances[moduleName];
RCTAssert(target != nil, @"No module found for name '%@'", moduleName);
[invocation setArgument:&target atIndex:0];
SEL selector = method.selector;
[invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes.
NSMutableArray *blocks = [NSMutableArray array];
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) {
param = nil;
} else if ([method.blockArgumentIndexes containsIndex:idx]) {
id block = [strongSelf createResponseSenderBlock:[param integerValue]];
[blocks addObject:block];
param = block;
}
[invocation setArgument:&param atIndex:idx + 2];
}];
@try {
[invocation invoke];
}
@catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
}
@finally {
// Force `blocks` to remain alive until here.
blocks = nil;
}
});
return YES;
} }
/** /**
@ -352,84 +412,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return invocation; return invocation;
} }
- (BOOL)_dispatchUsingAsyncHookMapWithModuleID:(NSInteger)moduleID
methodID:(NSInteger)methodID
params:(NSArray *)params
{
if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
return NO;
}
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(moduleID);
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
if (methodID < 0 || methodID >= methods.count) {
return NO;
}
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogMustFix(
@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName
);
return NO;
}
__weak RCTBridge *weakSelf = self;
dispatch_async(_shadowQueue, ^{
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}
NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
// TODO: we should just store module instances by index, since that's how we look them up anyway
id target = strongSelf->_moduleInstances[moduleName];
RCTAssert(target != nil, @"No module found for name '%@'", moduleName);
[invocation setArgument:&target atIndex:0];
SEL selector = method.selector;
[invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes.
NSMutableArray *blocks = [NSMutableArray array];
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) {
param = nil;
} else if ([method.blockArgumentIndexes containsIndex:idx]) {
id block = [strongSelf createResponseSenderBlock:[param integerValue]];
[blocks addObject:block];
param = block;
}
[invocation setArgument:&param atIndex:idx + 2];
}];
@try {
[invocation invoke];
}
@catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
}
@finally {
// Force `blocks` to remain alive until here.
blocks = nil;
}
});
return YES;
}
- (void)doneRegisteringModules - (void)doneRegisteringModules
{ {
RCTAssertMainThread(); RCTAssertMainThread();
@ -456,8 +438,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
moduleConfig[@"methods"] = methods; moduleConfig[@"methods"] = methods;
id target = [_moduleInstances objectForKey:moduleName]; id target = [_moduleInstances objectForKey:moduleName];
if ([target respondsToSelector:@selector(constantsToExport)] && ![target conformsToProtocol:@protocol(RCTNativeViewModule)]) { if ([target respondsToSelector:@selector(constantsToExport)]) {
// TODO: find a more elegant way to handle RCTNativeViewModule constants as a special case
moduleConfig[@"constants"] = [target constantsToExport]; moduleConfig[@"constants"] = [target constantsToExport];
} }
moduleConfigs[moduleName] = moduleConfig; moduleConfigs[moduleName] = moduleConfig;
@ -484,6 +465,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
} }
} }
- (void)registerRootView:(RCTRootView *)rootView
{
// TODO: only used by RCTUIManager - can we eliminate this special case?
for (id target in _moduleInstances.objectEnumerator) {
if ([target respondsToSelector:@selector(registerRootView:)]) {
[target registerRootView:rootView];
}
}
}
+ (BOOL)hasValidJSExecutor + (BOOL)hasValidJSExecutor
{ {
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]); return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);

View File

@ -4,6 +4,7 @@
#import "RCTLog.h" #import "RCTLog.h"
@class RCTBridge;
@class RCTSparseArray; @class RCTSparseArray;
@class RCTUIManager; @class RCTUIManager;
@ -42,6 +43,12 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
@protocol RCTNativeModule <NSObject> @protocol RCTNativeModule <NSObject>
@optional @optional
/**
* Optional initializer for modules that require access
* to bridge features, such as sending events or making JS calls
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/** /**
* Place this macro inside the method body of any method you want * Place this macro inside the method body of any method you want
* to expose to JS. The optional js_name argument will be used as * to expose to JS. The optional js_name argument will be used as
@ -74,7 +81,7 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
/** /**
* Provides minimal interface needed to register a UIViewManager module * Provides minimal interface needed to register a UIViewManager module
*/ */
@protocol RCTNativeViewModule <RCTNativeModule> @protocol RCTNativeViewModule <NSObject>
/** /**
* This method instantiates a native view to be managed by the module. * This method instantiates a native view to be managed by the module.
@ -83,6 +90,12 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
@optional @optional
/**
* The module name exposed to JS. If omitted, this will be inferred
* automatically by using the view module's class name.
*/
+ (NSString *)moduleName;
/** /**
* This method instantiates a shadow view to be managed by the module. If omitted, * This method instantiates a shadow view to be managed by the module. If omitted,
* an ordinary RCTShadowView instance will be created. * an ordinary RCTShadowView instance will be created.
@ -167,6 +180,12 @@ RCT_REMAP_VIEW_PROPERTY(name, name)
*/ */
- (NSDictionary *)customDirectEventTypes; - (NSDictionary *)customDirectEventTypes;
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.moduleName.X.
*/
- (NSDictionary *)constantsToExport;
/** /**
* To deprecate, hopefully * To deprecate, hopefully
*/ */

View File

@ -14,9 +14,7 @@
* JavaScript so that applications can clean up resources. (launch blocker). * JavaScript so that applications can clean up resources. (launch blocker).
* TODO: Incremental module loading. (low pri). * TODO: Incremental module loading. (low pri).
*/ */
@interface RCTJavaScriptAppEngine : NSObject <RCTInvalidating> @interface RCTJavaScriptAppEngine : NSObject
@property (nonatomic, readonly, strong) RCTBridge *bridge;
- (instancetype)initWithBridge:(RCTBridge *)bridge; - (instancetype)initWithBridge:(RCTBridge *)bridge;
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete; - (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete;

View File

@ -22,11 +22,8 @@
*/ */
@implementation RCTJavaScriptAppEngine @implementation RCTJavaScriptAppEngine
{ {
BOOL _isPaused; // Pauses drawing/updating of the JSView
BOOL _pauseOnEnterBackground;
CADisplayLink *_displayLink;
NSTimer *_runTimer;
NSDictionary *_loadedResource; NSDictionary *_loadedResource;
__weak RCTBridge *_bridge;
} }
- (instancetype)init - (instancetype)init
@ -54,118 +51,13 @@
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
{ {
RCTAssertMainThread(); RCTAssertMainThread();
if ((self = [super init])) { if ((self = [super init])) {
_bridge = bridge; _bridge = bridge;
_isPaused = NO;
self.pauseOnEnterBackground = YES;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(run:)];
if (_displayLink) {
[_displayLink setFrameInterval:1];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_runTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:@selector(run:) userInfo:nil repeats:YES];
}
} }
return self; return self;
} }
/**
* TODO: Wait until operations on `javaScriptQueue` are complete.
*/
- (void)dealloc
{
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
[_bridge invalidate];
_bridge = nil;
[_displayLink invalidate];
_displayLink = nil;
// Remove from notification center
self.pauseOnEnterBackground = NO;
}
#pragma mark - Run loop
- (void)run:(CADisplayLink *)sender
{
if (!_isPaused) {
RCTAssertMainThread();
[_bridge enqueueUpdateTimers];
}
}
- (void)pauseRunLoop
{
if (!_isPaused) {
[_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_isPaused = YES;
}
}
- (void)resumeRunLoop
{
if (_isPaused) {
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_isPaused = NO;
}
}
/**
* See warnings from lint: UIApplicationDidBecomeActive fires in a critical
* foreground path, and prevents the app from prioritizing the foreground
* processing. Consider using
* FBApplicationDidFinishEnteringForegroundAndIsNowIdleNotification.
*/
- (void)setPauseOnEnterBackground:(BOOL)pauses
{
NSArray *pauseN = @[
UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification
];
NSArray *resumeN =
@[UIApplicationWillEnterForegroundNotification, UIApplicationDidBecomeActiveNotification];
if (pauses) {
[self observeKeyPaths:pauseN selector:@selector(pauseRunLoop)];
[self observeKeyPaths:resumeN selector:@selector(resumeRunLoop)];
}
else {
[self removeObserverForKeyPaths:pauseN];
[self removeObserverForKeyPaths:resumeN];
}
_pauseOnEnterBackground = pauses;
}
- (void)removeObserverForKeyPaths:(NSArray*)keyPaths
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
for (NSString *name in keyPaths) {
[nc removeObserver:self name:name object:nil];
}
}
- (void)observeKeyPaths:(NSArray*)keyPaths selector:(SEL)selector
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
for (NSString *name in keyPaths) {
[nc addObserver:self selector:selector name:name object:nil];
}
}
#pragma mark - Module and script loading #pragma mark - Module and script loading
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL + (void)resetCacheForBundleAtURL:(NSURL *)moduleURL

View File

@ -18,7 +18,6 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
@implementation RCTRootView @implementation RCTRootView
{ {
dispatch_queue_t _shadowQueue;
RCTBridge *_bridge; RCTBridge *_bridge;
RCTJavaScriptAppEngine *_appEngine; RCTJavaScriptAppEngine *_appEngine;
RCTTouchHandler *_touchHandler; RCTTouchHandler *_touchHandler;
@ -62,9 +61,6 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
- (void)setUp - (void)setUp
{ {
// TODO: does it make sense to do this here? What if there's more than one host view?
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Every root view that is created must have a unique react tag. // Every root view that is created must have a unique react tag.
// Numbering of these tags goes from 1, 11, 21, 31, etc // Numbering of these tags goes from 1, 11, 21, 31, etc
static NSInteger rootViewTag = 1; static NSInteger rootViewTag = 1;
@ -95,16 +91,16 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
} }
} else { } else {
[_bridge.uiManager registerRootView:self]; [_bridge registerRootView:self];
NSString *moduleName = _moduleName ?: @""; NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{ NSDictionary *appParameters = @{
@"rootTag": self.reactTag ?: @0, @"rootTag": self.reactTag ?: @0,
@"initialProps": self.initialProperties ?: @{}, @"initialProps": self.initialProperties ?: @{},
}; };
[_appEngine.bridge enqueueJSCall:RCTModuleIDBundler [_bridge enqueueJSCall:RCTModuleIDBundler
methodID:RCTBundlerRunApplication methodID:RCTBundlerRunApplication
args:@[moduleName, appParameters]]; args:@[moduleName, appParameters]];
} }
} }
@ -125,12 +121,10 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
}; };
[_executor invalidate]; [_executor invalidate];
[_appEngine invalidate];
[_bridge invalidate]; [_bridge invalidate];
_executor = [[RCTContextExecutor alloc] init]; _executor = [[RCTContextExecutor alloc] init];
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor _bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor
shadowQueue:_shadowQueue
javaScriptModulesConfig:[RCTModuleIDs config]]; javaScriptModulesConfig:[RCTModuleIDs config]];
_appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge]; _appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge];

View File

@ -3,12 +3,12 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "RCTExport.h" #import "RCTExport.h"
#import "RCTInvalidating.h"
@class RCTBridge; @class RCTBridge;
@interface RCTTiming : NSObject <RCTNativeModule> @interface RCTTiming : NSObject <RCTNativeModule, RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge; - (instancetype)initWithBridge:(RCTBridge *)bridge;
- (void)enqueueUpdateTimers;
@end @end

View File

@ -11,7 +11,6 @@
@interface RCTTimer : NSObject @interface RCTTimer : NSObject
@property (nonatomic, strong, readonly) NSDate *target; @property (nonatomic, strong, readonly) NSDate *target;
@property (nonatomic, assign, readonly, getter=isActive) BOOL active;
@property (nonatomic, assign, readonly) BOOL repeats; @property (nonatomic, assign, readonly) BOOL repeats;
@property (nonatomic, strong, readonly) NSNumber *callbackID; @property (nonatomic, strong, readonly) NSNumber *callbackID;
@property (nonatomic, assign, readonly) NSTimeInterval interval; @property (nonatomic, assign, readonly) NSTimeInterval interval;
@ -26,7 +25,6 @@
repeats:(BOOL)repeats repeats:(BOOL)repeats
{ {
if ((self = [super init])) { if ((self = [super init])) {
_active = YES;
_interval = interval; _interval = interval;
_repeats = repeats; _repeats = repeats;
_callbackID = callbackID; _callbackID = callbackID;
@ -40,13 +38,9 @@
*/ */
- (BOOL)updateFoundNeedsJSUpdate - (BOOL)updateFoundNeedsJSUpdate
{ {
if (_active && _target.timeIntervalSinceNow <= 0) { if (_target && _target.timeIntervalSinceNow <= 0) {
// The JS Timers will do fine grained calculating of expired timeouts. // The JS Timers will do fine grained calculating of expired timeouts.
if (_repeats) { _target = _repeats ? [NSDate dateWithTimeIntervalSinceNow:_interval] : nil;
_target = [NSDate dateWithTimeIntervalSinceNow:_interval];
} else {
_active = NO;
}
return YES; return YES;
} }
return NO; return NO;
@ -58,6 +52,7 @@
{ {
RCTSparseArray *_timers; RCTSparseArray *_timers;
RCTBridge *_bridge; RCTBridge *_bridge;
id _updateTimer;
} }
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
@ -65,19 +60,74 @@
if ((self = [super init])) { if ((self = [super init])) {
_bridge = bridge; _bridge = bridge;
_timers = [[RCTSparseArray alloc] init]; _timers = [[RCTSparseArray alloc] init];
[self startTimers];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(stopTimers)
name:name
object:nil];
}
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationWillEnterForegroundNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startTimers)
name:name
object:nil];
}
} }
return self; return self;
} }
/** - (void)dealloc
* TODO (#5906496): Wait until operations on `javaScriptQueue` are complete to complete the {
* `dealloc`. [[NSNotificationCenter defaultCenter] removeObserver:self];
*/ }
/* - (void)dealloc
{
} */
- (void)enqueueUpdateTimers - (BOOL)isValid
{
return _bridge != nil;
}
- (void)invalidate
{
[self stopTimers];
_bridge = nil;
}
- (void)stopTimers
{
[_updateTimer invalidate];
_updateTimer = nil;
}
- (void)startTimers
{
RCTAssertMainThread();
if (![self isValid] || _updateTimer != nil) {
return;
}
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
if (_updateTimer) {
[_updateTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
target:self
selector:@selector(update)
userInfo:nil
repeats:YES];
}
}
- (void)update
{ {
RCTAssertMainThread(); RCTAssertMainThread();
@ -86,7 +136,7 @@
if ([timer updateFoundNeedsJSUpdate]) { if ([timer updateFoundNeedsJSUpdate]) {
[timersToCall addObject:timer.callbackID]; [timersToCall addObject:timer.callbackID];
} }
if (!timer.active) { if (!timer.target) {
_timers[timer.callbackID] = nil; _timers[timer.callbackID] = nil;
} }
} }
@ -97,14 +147,6 @@
} }
} }
- (void)scheduleCallbackID:(NSNumber *)callbackID interval:(NSTimeInterval)interval targetTime:(NSTimeInterval)targetTime repeats:(BOOL)repeats
{
dispatch_async(dispatch_get_main_queue(), ^{
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID interval:interval targetTime:targetTime repeats:repeats];
_timers[callbackID] = timer;
});
}
/** /**
* There's a small difference between the time when we call * There's a small difference between the time when we call
* setTimeout/setInterval/requestAnimation frame and the time it actually makes * setTimeout/setInterval/requestAnimation frame and the time it actually makes
@ -132,10 +174,13 @@
interval = 0; interval = 0;
} }
[self scheduleCallbackID:callbackID RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
interval:interval interval:interval
targetTime:targetTime targetTime:targetTime
repeats:repeats.boolValue]; repeats:repeats];
dispatch_async(dispatch_get_main_queue(), ^{
_timers[callbackID] = timer;
});
} }
- (void)deleteTimer:(NSNumber *)timerID - (void)deleteTimer:(NSNumber *)timerID

View File

@ -5,7 +5,7 @@
#import "RCTExport.h" #import "RCTExport.h"
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
@class RCTAnimationRegistry; @class RCTBridge;
@class RCTRootView; @class RCTRootView;
@class RCTShadowView; @class RCTShadowView;
@ -14,13 +14,10 @@
@interface RCTUIManager : NSObject <RCTInvalidating, RCTNativeModule> @interface RCTUIManager : NSObject <RCTInvalidating, RCTNativeModule>
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue - (instancetype)initWithBridge:(RCTBridge *)bridge;
viewManagers:(NSDictionary *)viewManagers;
@property (nonatomic, strong) RCTEventDispatcher *eventDispatcher;
@property (nonatomic, strong) RCTSparseArray *shadowViewRegistry; @property (nonatomic, strong) RCTSparseArray *shadowViewRegistry;
@property (nonatomic, strong) RCTSparseArray *viewRegistry; @property (nonatomic, strong) RCTSparseArray *viewRegistry;
@property (nonatomic, strong) RCTAnimationRegistry *animationRegistry;
@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView; @property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
/** /**

View File

@ -34,6 +34,64 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
} }
} }
static NSString *RCTModuleName(Class moduleClass)
{
if ([moduleClass respondsToSelector:@selector(moduleName)]) {
return [moduleClass moduleName];
} else {
// Default implementation, works in most cases
NSString *className = NSStringFromClass(moduleClass);
if ([className hasPrefix:@"RCTUI"]) {
className = [className substringFromIndex:@"RCT".length];
}
if ([className hasSuffix:@"Manager"]) {
className = [className substringToIndex:className.length - @"Manager".length];
}
return className;
}
}
static NSDictionary *RCTViewModuleClasses(void)
{
static NSMutableDictionary *modules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
modules = [NSMutableDictionary dictionary];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}
if (![cls conformsToProtocol:@protocol(RCTNativeViewModule)]) {
// Not an RCTNativeModule
continue;
}
// Get module name
NSString *moduleName = RCTModuleName(cls);
// Check module name is unique
id existingClass = modules[moduleName];
RCTCAssert(existingClass == Nil, @"Attempted to register RCTNativeViewModule class %@ for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
modules[moduleName] = cls;
}
free(classes);
});
return modules;
}
@implementation RCTUIManager @implementation RCTUIManager
{ {
// Root views are only mutated on the shadow queue // Root views are only mutated on the shadow queue
@ -42,13 +100,14 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
NSMutableArray *_pendingUIBlocks; NSMutableArray *_pendingUIBlocks;
pthread_mutex_t _pendingUIBlocksMutex; pthread_mutex_t _pendingUIBlocksMutex;
dispatch_queue_t _shadowQueue;
NSDictionary *_nextLayoutAnimationConfig; // RCT thread only NSDictionary *_nextLayoutAnimationConfig; // RCT thread only
RCTResponseSenderBlock _nextLayoutAnimationCallback; // RCT thread only RCTResponseSenderBlock _nextLayoutAnimationCallback; // RCT thread only
RCTResponseSenderBlock _layoutAnimationCallbackMT; // Main thread only RCTResponseSenderBlock _layoutAnimationCallbackMT; // Main thread only
NSMutableDictionary *_defaultShadowViews; NSMutableDictionary *_defaultShadowViews;
NSMutableDictionary *_defaultViews; NSMutableDictionary *_defaultViews;
__weak RCTBridge *_bridge;
} }
- (id <RCTNativeViewModule>)_managerInstanceForViewWithModuleName:(NSString *)moduleName - (id <RCTNativeViewModule>)_managerInstanceForViewWithModuleName:(NSString *)moduleName
@ -62,17 +121,23 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
return managerInstance; return managerInstance;
} }
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue - (instancetype)initWithBridge:(RCTBridge *)bridge
viewManagers:(NSDictionary *)viewManagers
{ {
if ((self = [super init])) { if ((self = [super init])) {
_bridge = bridge;
pthread_mutex_init(&_pendingUIBlocksMutex, NULL);
// Instantiate view managers
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
viewManagers[moduleName] = [[moduleClass alloc] init];
}];
_viewManagers = viewManagers; _viewManagers = viewManagers;
_viewRegistry = [[RCTSparseArray alloc] init]; _viewRegistry = [[RCTSparseArray alloc] init];
_shadowViewRegistry = [[RCTSparseArray alloc] init]; _shadowViewRegistry = [[RCTSparseArray alloc] init];
_shadowQueue = shadowQueue;
pthread_mutex_init(&_pendingUIBlocksMutex, NULL);
// Internal resources // Internal resources
_pendingUIBlocks = [[NSMutableArray alloc] init]; _pendingUIBlocks = [[NSMutableArray alloc] init];
_rootViewTags = [[NSMutableSet alloc] init]; _rootViewTags = [[NSMutableSet alloc] init];
@ -101,6 +166,8 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
- (void)invalidate - (void)invalidate
{ {
RCTAssertMainThread();
_viewRegistry = nil; _viewRegistry = nil;
_shadowViewRegistry = nil; _shadowViewRegistry = nil;
@ -111,6 +178,8 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
- (void)registerRootView:(RCTRootView *)rootView; - (void)registerRootView:(RCTRootView *)rootView;
{ {
RCTAssertMainThread();
NSNumber *reactTag = rootView.reactTag; NSNumber *reactTag = rootView.reactTag;
UIView *existingView = _viewRegistry[reactTag]; UIView *existingView = _viewRegistry[reactTag];
RCTCAssert(existingView == nil || existingView == rootView, RCTCAssert(existingView == nil || existingView == rootView,
@ -121,7 +190,7 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
CGRect frame = rootView.frame; CGRect frame = rootView.frame;
// Register shadow view // Register shadow view
dispatch_async(_shadowQueue, ^{ dispatch_async(_bridge.shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init]; RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag; shadowView.reactTag = reactTag;
@ -468,7 +537,7 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
- (UIView *)viewForViewManager:(id <RCTNativeViewModule>)manager - (UIView *)viewForViewManager:(id <RCTNativeViewModule>)manager
{ {
return [manager viewWithEventDispatcher:_eventDispatcher]; return [manager viewWithEventDispatcher:_bridge.eventDispatcher];
} }
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id <RCTNativeViewModule>manager) static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id <RCTNativeViewModule>manager)
@ -610,25 +679,25 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
// 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.
for (id <RCTNativeViewModule>viewManager in _viewManagers.allValues) { for (id <RCTNativeViewModule>viewManager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [viewManager respondsToSelector:@selector(uiBlockToAmendWithShadowViewRegistry:)] ? [viewManager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry] : nil; RCTViewManagerUIBlock uiBlock = [viewManager respondsToSelector:@selector(uiBlockToAmendWithShadowViewRegistry:)] ? [viewManager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry] : nil;
if (uiBlock != nil) { if (uiBlock != nil) {
[self addUIBlock:uiBlock]; [self addUIBlock:uiBlock];
} }
} }
for (NSNumber *reactTag in _rootViewTags) { for (NSNumber *reactTag in _rootViewTags) {
RCTShadowView *rootView = _shadowViewRegistry[reactTag]; RCTShadowView *rootView = _shadowViewRegistry[reactTag];
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]]; [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView]; [self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView];
} }
pthread_mutex_lock(&_pendingUIBlocksMutex); pthread_mutex_lock(&_pendingUIBlocksMutex);
NSArray *previousPendingUIBlocks = _pendingUIBlocks; NSArray *previousPendingUIBlocks = _pendingUIBlocks;
_pendingUIBlocks = [[NSMutableArray alloc] init]; _pendingUIBlocks = [[NSMutableArray alloc] init];
pthread_mutex_unlock(&_pendingUIBlocksMutex); pthread_mutex_unlock(&_pendingUIBlocksMutex);
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
for (dispatch_block_t block in previousPendingUIBlocks) { for (dispatch_block_t block in previousPendingUIBlocks) {
block(); block();