The premise

React Native contains a lot of Native code, so for readers of the text:

  • Learn how to use React Native
  • Can understand OC function calls. 🤦 came ️

background

Don’t blindly

First of all, when React Native is needed, the selection of technology is not a solution conceived in a flash on the technical side, but a suitable technology stack should be selected according to the business scenario to assist the business from the technical perspective and enhance UE, robustness and function of the business.

A lot of times you don’t really need React Native, or React Native can dramatically increase your development costs. At this point, we need to consider whether we can sacrifice part of the user experience and use H5 to ensure the speed of iteration.

scenario

On the home page of our APP, there will be a lot of dynamically updated activity cells. Since they are activity-related cells, of course, it is impossible to completely implement them with the original. After all, the product side will not launch activities after the release of the APP. Given this scenario, it’s easy to think of using webViews to implement live pages that can be updated dynamically.

The static H5 is a good choice:

  • Low development cost
  • Fast iteration speed, basically do not receive client version of the impact.

But the drawback of the H5 is also obvious, and that is performance.

The module of H5 is embedded in the cell of the home page. If the H5 page is rendered by the client, there will be a problem of rendering time, resulting in poor user experience. Moreover, in the native development, the rendering of cell may be recycled. For example, when we use UICollectionView to render a long list, usually use UICollectionViewCell dequeueReusableCellWithReuseIdentifier to reuse the cell, Prevent memory leaks caused by too many cell instances. However, after recycling, if you want to re-render the previous H5 page, although not as slow as the first rendering speed, there will still be a white screen, especially on mid – and low-end machines.

In order to solve the above problems, it is considered to embed the React Native component in the Native cell to display activities.

As to why want to see the source code, in general, read the source code can make us for a framework to understand more deeply, so to be able to use it more elegant, but if you want to use the React Native in a production environment, to understand the source code is essential, as for the reason, will show you the instructions step by step in the article.

Take a look at the source code Orz

As a front-end developer, when you think of React, you always think of the Diff algorithm, setState process, and balabala’s interview questions.

But that’s not the core of the React Native source code.

An overview of the

React Native’s overall structure is shown below:

C++ is the glue layer that smooths out most of the differences between the Android and iOS platforms, providing the JavaScript layer with basically the same services that allow the same set of code to run on both platforms.

To put it simply, React Native is still executed in the form of JavaScript code. UI changes are mapped to Native through Bridge, and Native uses the way of its platform to render and become the actual display component of Native. When the Native event is triggered, the event is mapped to JavaScript through the Bridge. After calculation, JavaScript will return the content to be rendered to Native to realize a user interaction process.

Start with the load process

React Native has a lot of code. Here is a starting point to analyze the overall code flow.

Let’s start with a few classes throughout.

RCTRootView

All React Native UIs are mounted and rendered using the RCTRootView root view when mapped to Native.

// both methods are RCTRootView initialization methods - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions; - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;Copy the code
  • bundleURL: asynchronously pulls the React Native bundle locally or remotely and executes it.
  • moduleName: Each React Native module passes through its portalAppRegistry.registerComponentMethod to register a unique module name for each module and let Native make the call. Here is the registered module name.

The code above is the two core initializers of RCTRootView. The latter needs to initialize the RCTBridge itself. If your project has multiple places where you need to embed React Native, use the latter and manually instantiate a singleton of the RCTBridge. All interaction with JavaScript takes place through this RCTBridge instance.

RCTBridge

This object instance has a very important responsibility. If you use initWithBridge as described in the previous section to initialize the React Native view, then you need to manually initialize the Bridge object.

/** * 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<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions;Copy the code

Initializing the Bridge manually can be done by inheriting the Bridge’s Delegate. The RCTBridge object will hold an instance of RCTBatchedBridge, which will handle all the core logic.

RCTCxxBridge/RCTBatchedBridge

The RCTCxxBridge object, from which the RCTBatchBridge methods are derived, has already sunk into C++. When this object is loaded, it has three core methods:

// For loading JavaScript source code, An RCTJavaScriptLoader - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad is started OnProgress: RCTSourceLoadProgressBlock onProgress / / create a JavaScript Thread execute JavaScript code, Instantiate a JSCExecutor - (void)start // Executes JavaScript source code, There are both synchronous and asynchronous modes - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)syncCopy the code

The above three methods are directly related to the React Native process startup. Covers the loading and execution of code. Specific code can React/CxxBridge/RCTCxxBridge found in mm.

Simple loading process

A complete React Native load process can be found by sorting out the methods provided by the above two objects:

  1. createRCTRootViewReact Native provides the root view in the React Native UI.
  2. createRCTBridgeTo provide the bridging function required by iOS.
  3. createRCTBatchedBridgeIn fact, this object isRCTBridgeProvide methods to expose these methods.
  4. [RCTCxxBridge start]To start the JavaScript parsing process.
  5. [RCTCxxBridge loadSource]Through theRCTJavaScriptLoaderDownload the bundle and execute it.
  6. Create Module mapping between JavaScript and iOS.
  7. Map the module to the correspondingRCTRootViewIn the middle.

Here is a partial UML class diagram of a core module that will be used throughout the render phase:

Load process with JavaScript

The above rendering process is mainly the work of Native side, while our code is still JavaScript code in essence after packaging. Combining the codes on both sides, we can get a complete loading rendering process. Continue with step 5 of the previous section:

Suppose we split the React Native bundle into business bundles and base library bundles. The two bundles are named platform.bundle and Business. bundle. Of course, this is easier without subcontracting, and a bundle is executed when the Bridge is initialized. In practice, however, it is less likely not to subcontract because we will not be able to update the base library often, which will waste traffic and cause a blank screen when the base library is downloaded. The business bundle is constantly updated.

  1. After the RCTRootView initialization is complete, download the bundle code through [RCTCxxBridge loadSource].

  2. After the bundle download is complete, Will trigger the [[NSNotificationCenter defaultCenter] postNotificationName: RCTJavaScriptDidLoadNotification Object: the self – > _parentBridge the userInfo: @ {@ “bridge” : the self}]; Event to inform RCTRootView that the corresponding JavaScript code has been loaded. Then, perform the initialization of RCTRootContentView.

  3. When RCTRootContentView is initialized, it calls the bridge’s [_bridge. UiManager registerRootView:self]; Method to register a RootView with an instance of RCTUIManager. RCTUIManager, as the name suggests, is the manager used by React Native to manage all UI space renderings.

  4. After instantiating RCTRootContentView, [self runApplication:bridge] is executed; To run the JavaScript App. React Native’s red-screen Debug interface may cause an error when executing JavaScript code: [[RCTBridge currentBridge].redBox showErrorMessage:message withStack:stack]; .

  5. The runApplication method will go to [bridge enqueueJSCall:@ “AppRegistry” method:@ “runApplication” args:@[moduleName, appParameters] completion:NULL]; RCTBatchedBridge maintains a JavaScript execution queue in which all JavaScript calls are executed in sequence. This method passes in the specified ModuleName to execute the corresponding JavaScript code.

  6. At the OC level, actual execution logic in JavaScript code – (void) executeApplicationScript: (NSData *) script url: url async NSURL *) : (BOOL) async, This method is available in both synchronous and asynchronous versions, and can be called differently depending on the scenario. Actually perform logic will be void of the c + + layer JSCExecutor: : loadApplicationScript (STD: : unique_ptr < const JSBigString > script, STD :: String sourceURL). Finally, JavaScript code is executed through JSValueRef evaluateScript(JSContextRef Context, JSStringRef Script, JSStringRef sourceURL) methods, It then gets the JavaScript execution result, which in iOS is an object of type JSValueRef that can be converted to OC’s base data type.

  7. When the JavaScript code is finished executing, the JavaScript side code calls native modules. These native module calls are stored in a queue, and when the void JSCExecutor:: Flush () method executes, Void callNativeModules(JSExecutor& Executor, Folly :: Dynamic && Calls, bool isEndOfBatch) are executed together. And triggers rendering.

The whole process is as follows:

UI rendering process

React Native UI rendering is managed by RCTUIManager.

The React Native root RCTRootView is initialized with the RCTRootContentView rendering view.

  1. When the RCTBatchedBridge is initialized, the RCTUIManager object is initialized and can be accessed through the singleton instance exposed by the Bridge.

  2. When RCTRootContentView is initialized, it calls [_bridge. UiManager registerRootView:self]; To register the RCTRootContentView instance with the Bridge.

  3. Once the root view is ready, the runApplication method of RCTRootView is called to execute the corresponding JavaScript code. Here we go to the process described in the previous section, where Native code for JavaScript calls is executed via callNativeModules.

  4. After that, RCTUIManager takes over all uI-related rendering. Execute the batchComplete callback to perform the – (void)_layoutAndMount operation. Complete the layout and mount of the view.

At this point, React Native loads and renders the corresponding Native view into the UI.

JS calls Native methods

registered

Native methods need to be exposed in order to be called by JavaScript.

To do this, React Native provides the RCT_EXPORT_MODULE() macro.

/**
 * Place this macro in your class implementation to automatically register
 * your module with the bridge when it loads. The optional js_name argument
 * will be used as the JS module name. If omitted, the JS module name will
 * match the Objective-C class name.
 */
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
Copy the code

As you can see from the code, the macro registers the corresponding js_name into the RCTModuleClasses via the RCTRegisterModule(self) method. This NSMutableArray array stores all modules exposed to JavaScript, like a registry of modules.

After that, the _buildModuleRegistry method of RCTCxxBridge is called to register the corresponding Native Modules. Native modules registered with this method can be imported via JavaScript.

The introduction of

When introduced, native modules introduced by importing {NativeModules} from “react-native” actually call the JSCExecutor getNativeModule method, Find the corresponding native module previously registered to introduce.

JSValueRef JSCNativeModules::getModule(JSContextRef context, JSStringRef jsName) { if (! m_moduleRegistry) { return nullptr; } std::string moduleName = String::ref(context, jsName).str(); const auto it = m_objects.find(moduleName); if (it ! = m_objects.end()) { return static_cast<JSObjectRef>(it->second); } auto module = createModule(moduleName, context); if (! module.hasValue()) { // Allow lookup to continue in the objects own properties, which allows for overrides of NativeModules return nullptr; } // Protect since we'll be holding on to this value, even though JS may not module->makeProtected(); auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first; return static_cast<JSObjectRef>(result->second); }Copy the code

The JavaScript side

Having said the introduction of native modules in the native part, we can also take a look at the processing of native modules in JavaScript. All of the NativeModules we introduce come from the JavaScript module NativeModules, And this module can find its path through the source code for node_modules/react – native/Libraries/BatchedBridge/NativeModules js, In fact, exported NativeModules come from NativeModules = global.nativemoduleProxy; This JavaScript module is actually from the native nativeModuleProxy.

Go back to native

NativeModuleProxy passes in Native

installGlobalProxy(
    m_context,
    "nativeModuleProxy",
    exceptionWrapMethod<&JSCExecutor::getNativeModule>());

Copy the code

Is registered and bound to global so that JavaScript can be properly imported into the module. This module proxies a lot of native functionality to allow JavaScript to make direct calls. The methods we register with JavaScript via the RCT_EXPORT_MODULE() macro are also retrieved and called via NativeModules.

Native calls JavaScript methods

In the previous section, when talking about React Native startup, it was mentioned that Native can execute JavaScript code. After the execution is complete, the result returned by JavaScript execution can be obtained.

The results can be directly through the void JSCExecutor JSCExecutor: : callFunction (const STD: : string & moduleId, const STD: : string & methodId, Const folly:: Dynamic & arguments) method execution.

At this point

Here, we’ve basically covered how React Native interacts with JavaScript, and how React Native renders into Native views.

The code involved will still be more, this article can only for the more important part of the function of a simple description, the whole rendering process string together, interested partners or the best to play a breakpoint, see the parameters of each function execution.

As my OC skills are really not very good, it is inevitable that there will be some omissions in the article. It would be great if some big men could provide suggestions for revision.

Why read the React Native source code? In cross-platform development, the functions provided by React Native are only the most basic. When React Native and React Native need to be used together (which is of course the majority of scenarios), Native is required to provide many necessary functions for React Native. It is inevitable that native code will need to be modified.

After contacting several online products, React Native was incorporated into the Native development to provide hot update functionality, which is basically a popular solution. The next article should build on the current solution and write a demo of a Native APP mixed in with React Native as part of the module (whisper BB: if demand is not busy).