diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index f19fdf364..f2a83422b 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -14,49 +14,23 @@ #import "AppDelegate.h" +#import "RCTBridge.h" +#import "RCTJavaScriptLoader.h" #import "RCTRootView.h" +@interface AppDelegate() + +@end + @implementation AppDelegate - (BOOL)application:(__unused UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - NSURL *jsCodeLocation; + RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self + launchOptions:launchOptions]; - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle?dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` - * to your Xcode project folder and run - * - * $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle' -o main.jsbundle - * - * then add the `main.jsbundle` file to your project and uncomment this line: - */ - -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; - -#if RUNNING_ON_CI - jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#endif - - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation - moduleName:@"UIExplorerApp" - launchOptions:launchOptions]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:@"UIExplorerApp"]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [[UIViewController alloc] init]; @@ -66,4 +40,50 @@ return YES; } +- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge +{ + NSURL *sourceURL; + + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ + + sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle?dev=true"]; + + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` + * to your Xcode project folder and run + * + * $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle' -o main.jsbundle + * + * then add the `main.jsbundle` file to your project and uncomment this line: + */ + + // sourceURL = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + + #if RUNNING_ON_CI + sourceURL = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + #endif + + return sourceURL; +} + +- (void)loadSourceForBridge:(RCTBridge *)bridge + withBlock:(RCTSourceLoadBlock)loadCallback +{ + [RCTJavaScriptLoader loadBundleAtURL:[self sourceURLForBridge:bridge] + onComplete:loadCallback]; +} + @end diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 08b130314..888333edb 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -149,7 +149,11 @@ RCT_EXPORT_MODULE() - (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete { - NSDictionary *message = @{@"method": @"executeApplicationScript", @"url": [URL absoluteString], @"inject": _injectedObjects}; + NSDictionary *message = @{ + @"method": @"executeApplicationScript", + @"url": RCTNullIfNil([URL absoluteString]), + @"inject": _injectedObjects, + }; [self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) { onComplete(error); }]; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 8acad2163..0f9c206f5 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -156,6 +156,11 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL _parentBridge.bundleURL = bundleURL; } +- (id)delegate +{ + return _parentBridge.delegate; +} + - (BOOL)isLoading { return _loading; @@ -172,7 +177,17 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL // Register passed-in module instances NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; - for (id module in self.moduleProvider ? self.moduleProvider() : nil) { + + NSArray *extraModules = nil; + if (self.delegate) { + if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { + extraModules = [self.delegate extraModulesForBridge:_parentBridge]; + } + } else if (self.moduleProvider) { + extraModules = self.moduleProvider(); + } + + for (id module in extraModules) { preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module; } @@ -261,7 +276,6 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } }]; - NSURL *bundleURL = _parentBridge.bundleURL; if (_javaScriptExecutor == nil) { /** @@ -270,24 +284,15 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL */ _loading = NO; - } else if (!bundleURL) { - - // Allow testing without a script - dispatch_async(dispatch_get_main_queue(), ^{ - _loading = NO; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:_parentBridge - userInfo:@{ @"bridge": self }]; - }); } else { [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification object:self userInfo:@{ @"bridge": self }]; - RCTProfileBeginEvent(); RCTPerformanceLoggerStart(RCTPLScriptDownload); - RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; - [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) { + RCTProfileBeginEvent(); + NSURL *sourceURL = self.delegate ? [self.delegate sourceURLForBridge:_parentBridge] : self.bundleURL; + void (^loadCallback)(NSError *, NSString *) = ^(NSError *error, NSString *script) { RCTPerformanceLoggerEnd(RCTPLScriptDownload); RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]); @@ -306,7 +311,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL }); RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; - sourceCodeModule.scriptURL = bundleURL; + sourceCodeModule.scriptURL = sourceURL; sourceCodeModule.scriptText = script; if (error) { @@ -326,7 +331,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } else { - [self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) { + [self enqueueApplicationScript:script url:sourceURL onComplete:^(NSError *loadError) { if (loadError) { [[RCTRedBox sharedInstance] showError:loadError]; @@ -345,7 +350,22 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL userInfo:@{ @"bridge": self }]; }]; } - }]; + }; + + if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { + [self.delegate loadSourceForBridge:_parentBridge withBlock:loadCallback]; + } else if (sourceURL) { + [RCTJavaScriptLoader loadBundleAtURL:sourceURL + onComplete:loadCallback]; + } else { + // Allow testing without a script + dispatch_async(dispatch_get_main_queue(), ^{ + _loading = NO; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification + object:_parentBridge + userInfo:@{ @"bridge": self }]; + }); + } } } diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 3721b1124..80017eaf4 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -9,6 +9,7 @@ #import +#import "RCTBridgeDelegate.h" #import "RCTBridgeModule.h" #import "RCTDefines.h" #import "RCTFrameUpdate.h" @@ -70,7 +71,22 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @interface RCTBridge : NSObject + /** + * Creates a new bridge with a custom RCTBridgeDelegate. + * + * All the interaction with the JavaScript context should be done using the bridge + * instance of the RCTBridgeModules. Modules will be automatically instantiated + * using the default contructor, but you can optionally pass in an array of + * pre-initialized module instances if they require additional init parameters + * or configuration. + */ +- (instancetype)initWithDelegate:(id)delegate + launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; + +/** + * DEPRECATED: Use initWithDelegate:launchOptions: instead + * * The designated initializer. This creates a new bridge on top of the specified * executor. The bridge should then be used for all subsequent communication * with the JavaScript code running in the executor. Modules will be automatically @@ -100,8 +116,17 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, strong) NSURL *bundleURL; +/** + * The class of the executor currently being used *or* to be used after the next + * reload. + */ @property (nonatomic, strong) Class executorClass; +/** + * The delegate provided during the bridge initialization + */ +@property (nonatomic, weak, readonly) id delegate; + /** * The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a * higher-level interface for sending UI events such as touches and text input. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 2520864d0..f9eebea1b 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -137,6 +137,22 @@ dispatch_queue_t RCTJSThread; }); } +- (instancetype)initWithDelegate:(id)delegate + launchOptions:(NSDictionary *)launchOptions +{ + RCTAssertMainThread(); + + if ((self = [super init])) { + RCTPerformanceLoggerStart(RCTPLTTI); + + _delegate = delegate; + _launchOptions = [launchOptions copy]; + [self setUp]; + [self bindKeys]; + } + return self; +} + - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleProviderBlock)block launchOptions:(NSDictionary *)launchOptions diff --git a/React/Base/RCTBridgeDelegate.h b/React/Base/RCTBridgeDelegate.h new file mode 100644 index 000000000..82d58a422 --- /dev/null +++ b/React/Base/RCTBridgeDelegate.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +typedef void (^RCTSourceLoadBlock)(NSError *error, NSString *source); + +@class RCTBridge; + +@protocol RCTBridgeDelegate + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge; + +@optional + +- (NSArray *)extraModulesForBridge:(RCTBridge *)bridge; +- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback; + +@end diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index 01f51d7e9..aa14453ad 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -9,7 +9,7 @@ #import -#import "RCTJavaScriptExecutor.h" +#import "RCTBridgeDelegate.h" @class RCTBridge; @@ -20,8 +20,6 @@ */ @interface RCTJavaScriptLoader : NSObject -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - -- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(void (^)(NSError *, NSString *))onComplete; ++ (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(RCTSourceLoadBlock)onComplete; @end diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index b110c5af0..3a69aaf96 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -15,23 +15,10 @@ #import "RCTUtils.h" @implementation RCTJavaScriptLoader -{ - __weak RCTBridge *_bridge; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - RCTAssert(bridge, @"bridge parameter is required"); - - if ((self = [super init])) { - _bridge = bridge; - } - return self; -} RCT_NOT_IMPLEMENTED(-init) -- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *, NSString *))onComplete ++ (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete { // Sanitize the script URL scriptURL = [RCTConvert NSURL:scriptURL.absoluteString]; diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 297747564..94968a214 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -197,6 +197,7 @@ 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = ""; }; 146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = ""; }; 146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = ""; }; + 1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeDelegate.h; sourceTree = ""; }; 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = ""; }; 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = ""; }; 14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleData.h; sourceTree = ""; }; @@ -482,6 +483,7 @@ 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */, 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */, 83CBBA501A601E3B00E9B192 /* RCTUtils.m */, + 1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */, ); path = Base; sourceTree = "";