React Native源碼解析-native和js通信

? React Native(以下簡(jiǎn)稱RN)的目標(biāo)是用基于react的JavaScript寫代碼,在iOS/Android平臺(tái)上原生渲染砰嘁,正如他們的口號(hào)"Learn Once,Write anywhere!"职抡,只要學(xué)會(huì)了大前端丰涉,iOS/Android/Web通吃吁脱,這樣就很神奇了尤溜,react.js還是那個(gè)react.js,模塊化竭宰、虛擬DOM空郊、JSX語(yǔ)法概念一樣沒(méi)少,甚至可以基于流行的flux單向數(shù)據(jù)流來(lái)架構(gòu)我們的應(yīng)用切揭,而在客戶端并不是一個(gè)web頁(yè)面狞甚,而是純?cè)秩荆阅鼙燃僿eb頁(yè)提升很多廓旬,而且還順帶具有像web頁(yè)一樣的動(dòng)態(tài)更新能力哼审。

? 隨著版本的迭代更新,RN功能和相關(guān)特性也越來(lái)越多孕豹,代碼復(fù)雜度也隨之上升涩盾,這里先不論RN的爭(zhēng)議和發(fā)展趨勢(shì),而來(lái)學(xué)習(xí)下其優(yōu)秀的架構(gòu)設(shè)計(jì)和代碼實(shí)現(xiàn)励背。我們就用一個(gè)最簡(jiǎn)單的項(xiàng)目來(lái)進(jìn)行剖析春霍,運(yùn)行命令react-native init RNDemo,這里我的RN版本號(hào)為:0.47.0叶眉,RN最核心的當(dāng)屬js與native的通信機(jī)制址儒,理解了這套機(jī)制則比較容易理解RN整個(gè)架構(gòu)。

Native模塊

RCTBridgeModule

native導(dǎo)出給js的類稱之為模塊類衅疙,如RCTUIManager RCTTiming等莲趣,每個(gè)模塊類都實(shí)現(xiàn)了RCTBridgeModule協(xié)議

@protocol RCTBridgeModule <NSObject>

#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName;

@optional

@property (nonatomic, weak, readonly) RCTBridge *bridge;
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;

#define RCT_EXPORT_METHOD(method) \
  RCT_REMAP_METHOD(, method)

#define RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(method) \
  RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(, method)

#define RCT_REMAP_METHOD(js_name, method) \
  _RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
  - (void)method;
  
#define RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(js_name, method) \
  _RCT_EXTERN_REMAP_METHOD(js_name, method, YES) \
  - (id)method;
  
#define RCT_EXTERN_MODULE(objc_name, objc_supername) \
  RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)

#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
  objc_name : objc_supername \
  @end \
  @interface objc_name (RCTExternModule) <RCTBridgeModule> \
  @end \
  @implementation objc_name (RCTExternModule) \
  RCT_EXPORT_MODULE(js_name)
  
#define RCT_EXTERN_METHOD(method) \
  _RCT_EXTERN_REMAP_METHOD(, method, NO)

#define RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(method) \
  _RCT_EXTERN_REMAP_METHOD(, method, YES)

#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
  + (NSArray *)RCT_CONCAT(__rct_export__, \
    RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
    return @[@#js_name, @#method, @is_blocking_synchronous_method]; \
  }
  
- (NSArray<id<RCTBridgeMethod>> *)methodsToExport;
- (NSDictionary<NSString *, id> *)constantsToExport;
- (void)batchDidComplete;
- (void)partialBatchDidFlush;

@end

該協(xié)議定義了模塊導(dǎo)出方法、導(dǎo)出常量炼蛤、模塊運(yùn)行隊(duì)列等妖爷,還定義了很多宏。

RCT_EXPORT_MODULE

模塊類被加載進(jìn)runtime的時(shí)候,執(zhí)行load方法絮识,將該模塊類的Class信息添加到一個(gè)全局?jǐn)?shù)組RCTModuleClasses里去绿聘,RCTGetModuleClasses()方法可獲取該數(shù)組。

RCT_EXPORT_METHOD(method)

RN在e9095b2版本移除了bang神博客所說(shuō)的從data數(shù)據(jù)段獲取導(dǎo)出方法的黑魔法次舌,而是給每個(gè)導(dǎo)出方法添加一個(gè)對(duì)應(yīng)的方法熄攘。

比如method為doSomething,則宏展開后為

+ (NSArray *)__rct_export__(行號(hào)和系統(tǒng)計(jì)數(shù)){ 
    return @[@"", @"doSomething", @(NO)];
  }
- (void)doSomething;

RCTModuleData方法- (NSArray<id<RCTBridgeMethod>> *)methods彼念,通過(guò)遍歷模塊運(yùn)行時(shí)方法列表挪圾,找到有__rct_export__前綴的方法,根據(jù)方法返回的數(shù)組實(shí)例化RCTModuleMethod,從而收集到所有RCT_EXPORT_METHOD對(duì)應(yīng)的導(dǎo)出方法逐沙。

模塊配置

所有的模塊配置存放在ModuleRegistryC++類中哲思,JSCExecutorgetNativeModule方法可獲得指定模塊的配置,最終是從ModuleRegistry類方法getConfig拿到吩案。JSCNativeModules管理Native的導(dǎo)出模塊棚赔,JSCNativeModules構(gòu)造函數(shù)傳入JsToNativeBridgegetModuleRegistry方法返回的ModuleRegistry指針,JsToNativeBridge管理js調(diào)用Native所需配置徘郭、方法等靠益,是js調(diào)用native的native響應(yīng)方。

RCTAppState模塊為例残揉,拿到的模塊信息如下:

struct ModuleConfig {
  size_t index;
  folly::dynamic config;
};
{
  21;
  [AppState,{initialAppState:unknown},[getCurrentAppState,addListener,removeListeners]];
}

index是模塊index胧后,config動(dòng)態(tài)數(shù)組依次存放moudlename、export constants抱环、export methodNames array 壳快、promiseMethodId array 、syncMethodId array江醇。

Native模塊生成

Native模塊初始化

在應(yīng)用啟動(dòng)delegate里濒憋,創(chuàng)建了一個(gè)RCTRootView實(shí)例,這個(gè)實(shí)例初始化了RCTBridge實(shí)例陶夜,在其- (void)setUp方法里:

self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];

self.batchedBridgeRCTCxxBridge的實(shí)例,用于批量橋接裆站,之前版本的RCTBatchedBridge已不再實(shí)現(xiàn)条辟,在- (void)start方法主要步驟是:

  • 發(fā)送js即將加載通知
  • 創(chuàng)建常駐線程_jsThread,native和js互相調(diào)用默認(rèn)會(huì)在該線程執(zhí)行宏胯,也可以自己指定模塊的運(yùn)行隊(duì)列

    _jsThread = [[NSThread alloc] initWithTarget:self
                                          selector:@selector(runJSRunLoop)
                                            object:nil];
    _jsThread.name = RCTJSThreadName;
    _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
    [_jsThread start];
    
  • 初始化所有native modules

    - (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
    {
      ...
      NSArray<id<RCTBridgeModule>> *extraModules = nil;
      if (self.delegate) {
        if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
          extraModules = [self.delegate extraModulesForBridge:_parentBridge];
        }
      } else if (self.moduleProvider) {
        extraModules = self.moduleProvider();
      }
      ...
      NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
      NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
      NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
      ...
      // Set up moduleData for pre-initialized module instances
      for (id<RCTBridgeModule> module in extraModules) {
        Class moduleClass = [module class];
        NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    
        if (RCT_DEBUG) {
          ...
        }
    
        // Instantiate moduleData container
        RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
                                                                           bridge:self];
        moduleDataByName[moduleName] = moduleData;
        [moduleClassesByID addObject:moduleClass];
        [moduleDataByID addObject:moduleData];
      }
      ...
      // Set up moduleData for automatically-exported modules
      for (Class moduleClass in RCTGetModuleClasses()) {
        NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
        
        if ([moduleName isEqual:@"RCTJSCExecutor"]) {
          continue;
        }
    
        // Check for module name collisions
        RCTModuleData *moduleData = moduleDataByName[moduleName];
        if (moduleData) {
          if (moduleData.hasInstance) {
            // Existing module was preregistered, so it takes precedence
            continue;
          } else if ([moduleClass new] == nil) {
            // The new module returned nil from init, so use the old module
            continue;
          } else if ([moduleData.moduleClass new] != nil) {
            // Both modules were non-nil, so it's unclear which should take precedence
            RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
                        "name '%@', but name was already registered by class %@",
                        moduleClass, moduleName, moduleData.moduleClass);
          }
        }
    
        // Instantiate moduleData
        // TODO #13258411: can we defer this until config generation?
        moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
                                                         bridge:self];
        moduleDataByName[moduleName] = moduleData;
        [moduleClassesByID addObject:moduleClass];
        [moduleDataByID addObject:moduleData];
      }
      ...
    
      // Store modules
      _moduleDataByID = [moduleDataByID copy];
      _moduleDataByName = [moduleDataByName copy];
      _moduleClassesByID = [moduleClassesByID copy];
      ...
      // Dispatch module init onto main thead for those modules that require it
      for (RCTModuleData *moduleData in _moduleDataByID) {
        if (moduleData.hasInstance &&
            (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
          (void)[moduleData instance];
        }
      }
      ...
      // From this point on, RCTDidInitializeModuleNotification notifications will
      // be sent the first time a module is accessed.
      _moduleSetupComplete = YES;
    
      [self _prepareModulesWithDispatchGroup:dispatchGroup];
    
      ...
    }
    

    主要步驟:

    1. 得到bridgeModule數(shù)組extraModules

      從delegate實(shí)現(xiàn)方或傳入的moduleProvider屬性獲取extraModules羽嫡,這里兩者都為空

    2. 實(shí)例化moduleClassesByID Class數(shù)組,moduleDataByID RCTModuleData*模塊數(shù)據(jù)數(shù)組,moduleDataByName名字模塊數(shù)據(jù)字典,臨時(shí)保存肩袍,作用見名思義

    3. 遍歷extraModules杭棵,填充第二步數(shù)組和字典

    4. RCTGetModuleClasses()得到聲明了RCT_EXPORT_MODULE的所有模塊Class,遍歷數(shù)組,先檢查命名沖突,再以moduleClass為參數(shù)實(shí)例化RCTModuleData魂爪,然后填充第二步數(shù)組和字典

      RCTModuleData類管理導(dǎo)出給js的模塊數(shù)據(jù)先舷,包括Class信息,導(dǎo)出方法滓侍,導(dǎo)出常量等

    5. 遍歷_moduleDataByID蒋川,調(diào)用RCTModuleData對(duì)應(yīng)實(shí)例的instance方法,初始化RCTModuleData

    6. 執(zhí)行_prepareModulesWithDispatchGroup方法撩笆,初始化除白名單外的模塊導(dǎo)出常量

  • 實(shí)例化Instance類捺球,該類在下文有介紹

    _reactInstance.reset(new Instance);
    
  • 實(shí)例化抽象工廠類JSExecutorFactory

    __weak RCTCxxBridge *weakSelf = self;
      std::shared_ptr<JSExecutorFactory> executorFactory;
      if (!self.executorClass) {
        BOOL useCustomJSC =
          [self.delegate respondsToSelector:@selector(shouldBridgeUseCustomJSC:)] &&
          [self.delegate shouldBridgeUseCustomJSC:self];
        // The arg is a cache dir.  It's not used with standard JSC.
        executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object
          ("UseCustomJSC", (bool)useCustomJSC)
    #if RCT_PROFILE
          ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch)
    #endif
        ));
      } else {
        id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
        executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
          if (error) {
            [weakSelf handleError:error];
          }
        }));
      }
    

    本地JavaScriptCore運(yùn)行會(huì)實(shí)例化JSCExecutorFactory, 瀏覽器遠(yuǎn)程調(diào)試模式會(huì)實(shí)例RCTObjcExecutorFactory夕冲,這里我們就以JSCExecutorFactory為例分析氮兵,遠(yuǎn)程調(diào)試會(huì)在另一篇做分析。

  • _jsThread線程上初始化橋接

    - (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
    {
      if (!self.valid) {
        return;
      }
    
      RCTAssertJSThread();
      __weak RCTCxxBridge *weakSelf = self;
      _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
        if (error) {
          [weakSelf handleError:error];
        }
      });
    
      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil);
      // This can only be false if the bridge was invalidated before startup completed
      if (_reactInstance) {
        // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
        _reactInstance->initializeBridge(
          std::unique_ptr<RCTInstanceCallback>(new RCTInstanceCallback(self)),
          executorFactory,
          _jsMessageThread,
          [self _buildModuleRegistry]);
    
    #if RCT_PROFILE
        ...
    #endif
      }
    
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
    }
    

    主要步驟:

    1. 實(shí)例化RCTMessageThread類歹鱼,RCTMessageThread類封裝了在_jsThread常駐線程上的同步和異步執(zhí)行任務(wù)的方法泣栈。
    2. Instance實(shí)例_reactInstance執(zhí)行了初始化方法void initializeBridge(..), 注入了所需依賴,Instance是iOS/Android與javacriptCore交互的入口類醉冤。初始化了模塊注冊(cè)表ModuleRegistry實(shí)例秩霍,ModuleRegistry是C++類,ios/android均需填充模塊信息數(shù)組
  • 加載jsbundle源文件

    RCTJavaScriptLoader封裝了加載jsbundle文件的方法蚁阳,主要步驟是需要下載則通過(guò)RCTMultipartDataTask下載js文件铃绒,最后返回NSData數(shù)據(jù)

  • 執(zhí)行解析jsbundle

    dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
          RCTCxxBridge *strongSelf = weakSelf;
          if (sourceCode && strongSelf.loading) {
            [strongSelf executeSourceCode:sourceCode sync:NO];
          }
        });
    

    在上述組任務(wù)結(jié)束時(shí),收到通知螺捐,在最高等級(jí)全局隊(duì)列執(zhí)行加載完的jsbundle

    - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
    {
      // This will get called from whatever thread was actually executing JS.
      dispatch_block_t completion = ^{
        // Flush pending calls immediately so we preserve ordering
        [self _flushPendingCalls];
    
        // Perform the state update and notification on the main thread, so we can't run into
        // timing issues with RCTRootView
        dispatch_async(dispatch_get_main_queue(), ^{
          [[NSNotificationCenter defaultCenter]
           postNotificationName:RCTJavaScriptDidLoadNotification
           object:self->_parentBridge userInfo:@{@"bridge": self}];
    
          // Starting the display link is not critical to startup, so do it last
          [self ensureOnJavaScriptThread:^{
            // Register the display link to start sending js calls after everything is setup
            [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
          }];
        });
      };
    
      if (sync) {
        [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
        completion();
      } else {
        [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
      }
    
    #if RCT_DEV
      ...
    #endif
    }
    

    主要步驟:

    1. 清空調(diào)用隊(duì)列_pendingCalls颠悬,_pendingCount置0

    2. 發(fā)送js加載完畢通知,_jsThread線程定血,設(shè)置RCTDisplayLink監(jiān)測(cè)js線程幀率赔癌,原理在另一篇作分析

    3. 執(zhí)行解析jsbundle,這里是異步執(zhí)行

      執(zhí)行解析中間過(guò)程為了解耦和擴(kuò)展性澜沟,引入了NativeToJsBridge等類灾票,層次很多,可能看到這就有點(diǎn)暈了茫虽,來(lái)看下這塊的執(zhí)行過(guò)程刊苍。

jsBundle執(zhí)行

首先,上文提到的JSCExecutorFactory濒析,運(yùn)用了標(biāo)準(zhǔn)的工廠模式:

JSCExecutor是本地JavaScriptCore具體執(zhí)行的產(chǎn)品類正什,是跨平臺(tái)的C++類,是一個(gè)非常重要的類号杏。

JSCExecutor構(gòu)造時(shí)向JSContext注入了全局函數(shù)

installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
...
installGlobalProxy(m_context, "nativeModuleProxy",
                       exceptionWrapMethod<&JSCExecutor::getNativeModule>());
...
installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");                     

installNativeHook方法同時(shí)注冊(cè)了回調(diào)函數(shù)exceptionWrapMethod<method>()婴氮,當(dāng)JS端調(diào)用對(duì)應(yīng)方法,exceptionWrapMethod全局函數(shù)被調(diào)用。

? MessageQueueThreadRCTMessageThread的抽象基類主经,封裝了在消息隊(duì)列里同步和異步執(zhí)行的方法荣暮,MessageQueueThread是需要各平臺(tái)各自實(shí)現(xiàn)的。

class MessageQueueThread {
 public:
  virtual ~MessageQueueThread() {}
  virtual void runOnQueue(std::function<void()>&&) = 0;
  // runOnQueueSync and quitSynchronous are dangerous.  They should only be
  // used for initialization and cleanup.
  virtual void runOnQueueSync(std::function<void()>&&) = 0;
  // Once quitSynchronous() returns, no further work should run on the queue.
  virtual void quitSynchronous() = 0;
};

Instance類依賴了很多類旨怠,它是一個(gè)C++類渠驼,是iOS/Android與javacriptCore交互的入口類,封裝了native與js的交互鉴腻,包括解析js字符流迷扇,調(diào)用js方法,設(shè)置jscontext全局變量爽哎,發(fā)出回調(diào)等蜓席。

class RN_EXPORT Instance {
 public:
  ~Instance();
  void initializeBridge(
    std::unique_ptr<InstanceCallback> callback,
    std::shared_ptr<JSExecutorFactory> jsef,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::shared_ptr<ModuleRegistry> moduleRegistry);

  void setSourceURL(std::string sourceURL);

  void loadScriptFromString(
    std::unique_ptr<const JSBigString> string,
    std::string sourceURL,
    bool loadSynchronously);
  void loadUnbundle(
    std::unique_ptr<JSModulesUnbundle> unbundle,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL,
    bool loadSynchronously);
  bool supportsProfiling();
  void startProfiler(const std::string& title);
  void stopProfiler(const std::string& title, const std::string& filename);
  void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
  void *getJavaScriptContext();
  void callJSFunction(std::string&& module, std::string&& method, folly::dynamic&& params);
  void callJSCallback(uint64_t callbackId, folly::dynamic&& params);

  // This method is experimental, and may be modified or removed.
  template <typename T>
  Value callFunctionSync(const std::string& module, const std::string& method, T&& args) {
    CHECK(nativeToJsBridge_);
    return nativeToJsBridge_->callFunctionSync(module, method, std::forward<T>(args));
  }

  #ifdef WITH_JSC_MEMORY_PRESSURE
  void handleMemoryPressure(int pressureLevel);
  #endif

 private:
  void callNativeModules(folly::dynamic&& calls, bool isEndOfBatch);
  void loadApplication(
    std::unique_ptr<JSModulesUnbundle> unbundle,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL);
  void loadApplicationSync(
    std::unique_ptr<JSModulesUnbundle> unbundle,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL);

  std::shared_ptr<InstanceCallback> callback_;
  std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
  std::shared_ptr<ModuleRegistry> moduleRegistry_;

  std::mutex m_syncMutex;
  std::condition_variable m_syncCV;
  bool m_syncReady = false;
};

Instance類依賴的InstanceCallback JSExecutorFactory MessageQueueThread ModuleRegistry等類,都需要平臺(tái)各自實(shí)現(xiàn)课锌,Instance是作為一個(gè)跨平臺(tái)的接口封裝類厨内,如iOS中,callback成員變量是指向InstanceCallback的子類RCTInstanceCallback渺贤,jsef成員則動(dòng)態(tài)指向JSExecutorFactory的具體工廠類雏胃,JSExecutorFactory則可以選擇對(duì)應(yīng)的具體產(chǎn)品類。jsQueue指向RCTMessageThread類志鞍,moduleRegistry需要各平臺(tái)自行填充瞭亮。Instance類函數(shù)實(shí)現(xiàn)基本都是通過(guò)NativeToJsBridge具體實(shí)現(xiàn)的,在Instance執(zhí)行initializeBridge時(shí)固棚,在MessageQueueThread同步初始化了NativeToJsBridge的實(shí)例nativeToJsBridge_统翩,那么我們來(lái)看看NativeToJsBridge類。

? 在RCTCxxBridge類的enqueueApplicationScript:url:onComplete:方法此洲,根據(jù)jsbundle類型去執(zhí)行對(duì)應(yīng)的方法厂汗。jsbundle目前有三種類型:String RAMBundle BCBundleString表示普通jsbundle呜师,用bundle命令整合出來(lái)的娶桦。RAMBundle是用unbundle命令打出來(lái)的bundle,它除了生成整合的js文件index.ios.bundle 外汁汗,還會(huì)生成各個(gè)單獨(dú)的未整合js文件趟紊,全部放在js-modules目錄下, bundle頭四個(gè)字節(jié)固定為0xFB0BD1E5碰酝。BCBundle是js字節(jié)碼bundle類型,并未用到戴差,就以普通jsbundle為例

self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
                                                 sourceUrlStr.UTF8String, false);

核心是通過(guò)Instance實(shí)例去執(zhí)行解析送爸, InstanceloadScriptFromString方法調(diào)用到NativeToJsBridgeloadApplicationloadApplicationSync的方法,NativeToJsBridge實(shí)例在Instance類構(gòu)造的時(shí)候在_jsThread線程初始化。

void NativeToJsBridge::loadApplication(
    std::unique_ptr<JSModulesUnbundle> unbundle,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {
  runOnExecutorQueue(
      [unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
       startupScript=folly::makeMoveWrapper(std::move(startupScript)),
       startupScriptSourceURL=std::move(startupScriptSourceURL)]
        (JSExecutor* executor) mutable {
    auto unbundle = unbundleWrap.move();
    if (unbundle) {
      executor->setJSModulesUnbundle(std::move(unbundle));
    }
    executor->loadApplicationScript(std::move(*startupScript),
                                    std::move(startupScriptSourceURL));
  });
}

普通jsbundle調(diào)用到JSExecutor子類loadApplicationScript方法袭厂,以子類JSCExecutor為例:

void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
  SystraceSection s("JSCExecutor::loadApplicationScript",
                    "sourceURL", sourceURL);

  std::string scriptName = simpleBasename(sourceURL);
  ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
  String jsSourceURL(m_context, sourceURL.c_str());

  // TODO t15069155: reduce the number of overrides here
#ifdef WITH_FBJSCEXTENSIONS
  ...
#elif defined(__APPLE__)
  BundleHeader header;
  memcpy(&header, script->c_str(), std::min(script->size(), sizeof(BundleHeader)));
  auto scriptTag = parseTypeFromHeader(header);

  if (scriptTag == ScriptTag::BCBundle) {
    using file_ptr = std::unique_ptr<FILE, decltype(&fclose)>;
    file_ptr source(fopen(sourceURL.c_str(), "r"), fclose);
    int sourceFD = fileno(source.get());

    JSValueRef jsError;
    JSValueRef result = JSC_JSEvaluateBytecodeBundle(m_context, NULL, sourceFD, jsSourceURL, &jsError);
    if (result == nullptr) {
      throw JSException(m_context, jsError, jsSourceURL);
    }
  } else
#endif
  {
    String jsScript;
    {
      SystraceSection s_("JSCExecutor::loadApplicationScript-createExpectingAscii");
      ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_START);
      jsScript = adoptString(std::move(script));
      ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP);
    }
    #ifdef WITH_FBSYSTRACE
    fbsystrace_end_section(TRACE_TAG_REACT_CXX_BRIDGE);
    #endif

    SystraceSection s_("JSCExecutor::loadApplicationScript-evaluateScript");
    evaluateScript(m_context, jsScript, jsSourceURL);
  }

  flush();

  ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
  ReactMarker::logMarker(ReactMarker::RUN_JS_BUNDLE_STOP);
}

該方法主要步驟:

  1. 根據(jù)JSBundle header得到JSBundle類型來(lái)執(zhí)行對(duì)應(yīng)的js上下文墨吓。

    evaluateScript是由jshelps組件封裝的,最終調(diào)用到JavaScriptCore的方法JSEvaluateScript纹磺。jschelps主要封裝了JavaScriptCore的相關(guān)函數(shù)帖烘,以及JSStringRef的C++對(duì)象封裝String類,JSObjectRef的C++對(duì)象封裝Object類橄杨,JSValueRef的C++對(duì)象封裝Value類等秘症。

  2. 調(diào)用flush()

    native調(diào)用jsflushedQueue方法,返回js端待調(diào)用方法隊(duì)列式矫,然后native執(zhí)行乡摹,清空該隊(duì)列

至此,Native模塊生成和相關(guān)準(zhǔn)備工作完成采转,模塊配置存放在ModuleRegistry類中聪廉。

JS模塊

本地jscore運(yùn)行時(shí),當(dāng)js需要調(diào)用到native模塊的時(shí)候故慈,通過(guò)nativeModuleProxy執(zhí)行native所注入方法板熊,返回對(duì)應(yīng)的模塊信息,而當(dāng)遠(yuǎn)程調(diào)試模式時(shí)察绷,native向global.__fbBatchedBridgeConfig注入了所有模塊列表信息干签,同樣是由native端生成的,如下:

type ModuleConfig = [
  string, /* name */
  ?Object, /* constants */
  Array<string>, /* functions */
  Array<number>, /* promise method IDs */
  Array<number>, /* sync method IDs */
];
globalconfig

在JS中同樣也存在供native調(diào)用的模塊,node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js中克婶,模塊保存在_lazyCallableModules中筒严。

js modules

JS模塊生成

registerCallableModule(name: string, module: Object) {
    this._lazyCallableModules[name] = () => module;
  }

  registerLazyCallableModule(name: string, factory: void => Object) {
    let module: Object;
    let getValue: ?(void => Object) = factory;
    this._lazyCallableModules[name] = () => {
      if (getValue) {
        module = getValue();
        getValue = null;
      }
      return module;
    };
  }

通過(guò)外部注冊(cè),填充_lazyCallableModules數(shù)組情萤,

Native 調(diào)用 JS

在RN里鸭蛙,封裝了底層細(xì)節(jié),外部暴露出的是通過(guò)RCTCxxBridge方法enqueueJSCall:method:args:completion調(diào)用筋岛,如native向js發(fā)送時(shí)間消息的方法sendEventWithName:body實(shí)現(xiàn)就是調(diào)用該方法娶视。該方法實(shí)現(xiàn)如下:

- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
  if (!self.valid) {
    return;
  }

  /**
   * AnyThread
   */
  RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil);

  RCTProfileBeginFlowEvent();
  [self _runAfterLoad:^{
    RCTProfileEndFlowEvent();

    if (self->_reactInstance) {
      self->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
                                           [RCTConvert folly_dynamic:args ?: @[]]);

      // ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
      // the block is invoked after callJSFunction
      if (completion) {
        if (self->_jsMessageThread) {
          self->_jsMessageThread->runOnQueue(completion);
        } else {
          RCTLogWarn(@"Can't invoke completion without messageThread");
        }
      }
    }
  }];

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

主要通過(guò)調(diào)用Instance類的callsJSFunction方法,最終調(diào)用到JSCExecutor::callFunction方法

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  SystraceSection s("JSCExecutor::callFunction");

  // This weird pattern is because Value is not default constructible.
  // The lambda is inlined, so there's no overhead.
  auto result = [&] {
    try {
      if (!m_callFunctionReturnResultAndFlushedQueueJS) {
        bindBridge();
      }
      return m_callFunctionReturnFlushedQueueJS->callAsFunction({
        Value(m_context, String::createExpectingAscii(m_context, moduleId)),
        Value(m_context, String::createExpectingAscii(m_context, methodId)),
        Value::fromDynamic(m_context, std::move(arguments))
      });
    } catch (...) {
      std::throw_with_nested(
        std::runtime_error("Error calling " + moduleId + "." + methodId));
    }
  }();

  callNativeModules(std::move(result));
}

callFunction方法先執(zhí)行js端方法callFunctionReturnFlushedQueue(在MessageQueue.js文件中),返回js端消息隊(duì)列睁宰,然后native解析隊(duì)列肪获,即調(diào)用callNativeModules,這個(gè)過(guò)程在下文JS調(diào)用Native有分析柒傻。

總體來(lái)說(shuō)還是使用JSCHelpers中封裝的C++方法evaluateScript(JSContextRef, JSStringRef, JSStringRef)孝赫,在常駐線程來(lái)執(zhí)行js語(yǔ)句,返回結(jié)果native解析红符。

JS調(diào)用Native

node_modules/react-native/Libraries/BatchedBridge/NativeModules.js文件中:

let NativeModules : {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else {
  ...
}
 module.exports = NativeModules;

本地JavascriptCore執(zhí)行時(shí)青柄,nativeModuleProxy全局函數(shù)在JSCExecutor構(gòu)造時(shí)伐债,通過(guò)installGlobalProxy方法注入了,這里的else分支是瀏覽器遠(yuǎn)程調(diào)試走的致开。當(dāng)取nativeModuleProxy屬性峰锁,如執(zhí)行const RCTAppState = NativeModules.AppState;JSObjectGetPropertyCallback回調(diào)在C++端被觸發(fā)双戳,調(diào)用到JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName)方法虹蒋,該方法通過(guò)JSCNativeModulesgetModule方法拿到native對(duì)應(yīng)配置,如第一節(jié)?模塊配置中拿到對(duì)應(yīng)的配置表飒货。

? js端也有 有BatchedBridge概念魄衅,node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js中,const BatchedBridge = new MessageQueue();膏斤,BatchedBridge對(duì)象實(shí)際上是MessageQueue的實(shí)例徐绑,轉(zhuǎn)到當(dāng)前目錄下的MessageQueue.js文件。

? js需要調(diào)用native方法的時(shí)候莫辨,調(diào)用enqueueNativeCall函數(shù)傲茄,比如js端執(zhí)行方法:

UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload)

這段代碼是在ReactNativeStack-dev.js中,用于js端通告native創(chuàng)建視圖沮榜,UIManager實(shí)際是NativeModules對(duì)象盘榨,本地JavacriptCore運(yùn)行時(shí),NativeModules對(duì)象方法在native的JSCExecutor::getNativeModule方法中通過(guò)調(diào)用js方法global.__fbGenNativeModule建立蟆融,global.__fbGenNativeModule即指向genModule方法對(duì)象,genModule方法中調(diào)用genMethod,genMethod中持有閉包草巡,將native方法調(diào)用通過(guò)BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);方法加入隊(duì)列處理,故上述方法調(diào)用最終通過(guò)enqueueNativeCall調(diào)用型酥。

enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
    if (onFail || onSucc) {
      if (__DEV__) {
        ...
      }
      // Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
      // to indicate fail (0) or success (1)
      onFail && params.push(this._callID << 1);
      onSucc && params.push((this._callID << 1) | 1);
      this._successCallbacks[this._callID] = onSucc;
      this._failureCallbacks[this._callID] = onFail;
    }

    if (__DEV__) {
      ...
    }
    this._callID++;

    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);

    if (__DEV__) {
      ...
    }
    this._queue[PARAMS].push(params);

    const now = new Date().getTime();
    if (global.nativeFlushQueueImmediate &&
        (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
         this._inCall === 0)) {
      var queue = this._queue;
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
      global.nativeFlushQueueImmediate(queue);
    }
    Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
    if (__DEV__ && this.__spy && isFinite(moduleID)) {
      ...
    } else if (this.__spy) {
      this.__spy({type: TO_NATIVE, module: moduleID + '', method: methodID, args: params});
    }
  }

? enqueueNativeCall_queue依次插入moduleID methodID params ,flushedQueue方法會(huì)把當(dāng)前的_callID插入到_queue最后山憨,緊接著判斷相鄰兩次flushQueue時(shí)間超過(guò)MIN_TIME_BETWEEN_FLUSHES_MS即5ms,或者當(dāng)前沒(méi)有正在處理的方法弥喉,則執(zhí)行全局nativeFlushQueueImmediate函數(shù)郁竟。nativeFlushQueueImmediate函數(shù)傳入_queue參數(shù),它在native端之前通過(guò)installNativeHook注入了由境,js端調(diào)用后native端收到函數(shù)回調(diào)棚亩,最終對(duì)應(yīng)執(zhí)行JSCExecutor類的nativeFlushQueueImmediate方法,該方法最終調(diào)用到JsToNativeBridgecallNativeModules方法虏杰,callNativeModules解析出js透?jìng)鞯膮?shù)_queue讥蟆,然后動(dòng)態(tài)調(diào)用方法。

for (auto& call : parseMethodCalls(std::move(calls))) {
      m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
    }

m_registry是ModuleRegistry的實(shí)例

void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
  if (moduleId >= modules_.size()) {
    throw std::runtime_error(
      folly::to<std::string>("moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
  }
  modules_[moduleId]->invoke(methodId, std::move(params), callId);
}

invoke方法即以反射去動(dòng)態(tài)執(zhí)行方法纺阔,具體執(zhí)行方法各平臺(tái)各自實(shí)現(xiàn)瘸彤,iOS上實(shí)際執(zhí)行的類是RCTNativeModule,它的invoke方法在參數(shù)對(duì)應(yīng)的module指定線程隊(duì)列執(zhí)行invokeInner方法,然后經(jīng)過(guò)轉(zhuǎn)換參數(shù)等操作笛钝,最終調(diào)用到RCTModuleMethod類的invokeWithBridge:module:arguments方法钧栖,通過(guò)NSInvocationinvokeWithTarget方法實(shí)現(xiàn)動(dòng)態(tài)調(diào)用低零,并返回調(diào)用結(jié)果,中間經(jīng)過(guò)了處理method name, methodSignature等過(guò)程拯杠,此處代碼可瀏覽RCTModuleMethod的類實(shí)現(xiàn)。另外在processMethodSignature方法中啃奴,將cbID和返回結(jié)果暫存潭陪,調(diào)用成功通過(guò)JSCExecutorm_invokeCallbackAndReturnFlushedQueueJS屬性 ,調(diào)用到j(luò)s里MessageQueue類的invokeCallbackAndReturnFlushedQueue方法,js端拿到返回值最蕾,js調(diào)用native的閉環(huán)形成依溯。

? 那么還有一個(gè)問(wèn)題,js只是把消息加入了隊(duì)列瘟则,js什么時(shí)候去讓native去取js的消息隊(duì)列處理黎炉?

  1. js端超時(shí)機(jī)制

    需要注意的是,遠(yuǎn)程調(diào)試模式并沒(méi)有超時(shí)機(jī)制醋拧,global.nativeFlushQueueImmediate始終是 undefined的慷嗜。

    每次消息入隊(duì)的時(shí)候,會(huì)檢查距離上次隊(duì)列清空完成是否超過(guò)5ms丹壕,超過(guò)則調(diào)用nativeFlushQueueImmediate 清空隊(duì)列庆械,native注冊(cè)回調(diào)被調(diào)用,否則立即入隊(duì)菌赖,由于js是單線程的缭乘,5ms內(nèi)也不會(huì)積壓很多消息,所以不用擔(dān)心處理效率問(wèn)題琉用。

  2. native主動(dòng)調(diào)用

    native調(diào)用js方法堕绩,native調(diào)用enqueueJSCall:method:args:completion方法會(huì)取到j(luò)s消息隊(duì)列,其實(shí)包含

      folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS;
      folly::Optional<Object> m_callFunctionReturnFlushedQueueJS;
      folly::Optional<Object> m_flushedQueueJS;
      folly::Optional<Object> m_callFunctionReturnResultAndFlushedQueueJS;
    

    處理方法都會(huì)返回js消息隊(duì)列邑时,即native每次調(diào)用js奴紧,都會(huì)主動(dòng)去取js隊(duì)列,比如事件消息刁愿、timer等绰寞。

綜上所述,js調(diào)用native實(shí)際上是有兩種機(jī)制的:

  1. native向jscontext的 global注入全局對(duì)象铣口,同時(shí)注冊(cè)相應(yīng)的回調(diào)滤钱,如nativeFlushQueueImmediate,js函數(shù)被調(diào)用脑题,對(duì)應(yīng)native回調(diào)被響應(yīng)
  2. js組成消息隊(duì)列件缸,native調(diào)用flushedQueue主動(dòng)去取

第一種是JSPatch所采用的,不過(guò)它注冊(cè)的回調(diào)是一個(gè)block, 第二種機(jī)制是最復(fù)雜的叔遂,對(duì)于模塊他炊,需要兩端維護(hù)一份配置表争剿,但是最高效的,js方需要執(zhí)行native方法痊末,僅需傳遞moduleId methodId arguments必要參數(shù)給native蚕苇,而方法真正執(zhí)行是在native方異步執(zhí)行的,返回結(jié)果異步返回給js方凿叠,如果換成方式1涩笤,native方法在jscontext同步執(zhí)行,明顯影響效率盒件,而且 當(dāng)短時(shí)間內(nèi)有很多條消息蹬碧,JS并不會(huì)去頻繁調(diào)用native,會(huì)在5ms內(nèi)去累積消息炒刁,然后發(fā)送給native恩沽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市翔始,隨后出現(xiàn)的幾起案子罗心,更是在濱河造成了極大的恐慌,老刑警劉巖绽昏,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件协屡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡全谤,警方通過(guò)查閱死者的電腦和手機(jī)肤晓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)认然,“玉大人补憾,你說(shuō)我怎么就攤上這事【碓保” “怎么了盈匾?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)毕骡。 經(jīng)常有香客問(wèn)我削饵,道長(zhǎng),這世上最難降的妖魔是什么未巫? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任窿撬,我火速辦了婚禮,結(jié)果婚禮上叙凡,老公的妹妹穿的比我還像新娘劈伴。我一直安慰自己,他們只是感情好握爷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布跛璧。 她就那樣靜靜地躺著严里,像睡著了一般。 火紅的嫁衣襯著肌膚如雪追城。 梳的紋絲不亂的頭發(fā)上刹碾,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音漓柑,去河邊找鬼教硫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辆布,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茶鉴,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼锋玲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了涵叮?” 一聲冷哼從身側(cè)響起惭蹂,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎割粮,沒(méi)想到半個(gè)月后盾碗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舀瓢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年廷雅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片京髓。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡航缀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堰怨,到底是詐尸還是另有隱情芥玉,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布备图,位于F島的核電站灿巧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏揽涮。R本人自食惡果不足惜抠藕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绞吁。 院中可真熱鬧幢痘,春花似錦、人聲如沸家破。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至门粪,卻和暖如春喊积,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玄妈。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工乾吻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拟蜻。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓绎签,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親酝锅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诡必,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 使用App.png 本文結(jié)構(gòu) 目前App的幾種常見的開發(fā)模式 關(guān)于React-Native的一點(diǎn)小看法 React...
    ZeroJ閱讀 5,170評(píng)論 0 22
  • React Native 是最近非常火的一個(gè)話題搔扁,介紹如何利用 React Native 進(jìn)行開發(fā)的文章和書籍多如...
    零度_不結(jié)冰閱讀 669評(píng)論 0 1
  • 我為什么寫這個(gè)主題呢爸舒,這是因?yàn)榻裉煸缟嫌袃蓚€(gè)同學(xué)發(fā)燒了,老師說(shuō)今天這兩個(gè)同學(xué)很可能有腮腺炎的細(xì)菌稿蹲。下午更恐怖...
    破雷神龍閱讀 335評(píng)論 0 4
  • correct = tf.nn.in_top_k(prediction, target, K): K --- 表示...
    律動(dòng)的時(shí)間線閱讀 477評(píng)論 0 0
  • 我小的時(shí)候是城市里的孩子扭勉,但是我卻特別喜歡農(nóng)村,那時(shí)候我的暑假寒假基本上都是在鄉(xiāng)下度過(guò)的苛聘,小伙伴們也愛(ài)跟我玩涂炎,好像...
    安然ZCR閱讀 458評(píng)論 4 5