ReactNative填坑之旅–與Native通訊之iOS篇

桃子紅了吶發表於2017-11-09

終於開始新一篇的填坑之旅了。RN厲害的一個地方就是RN可以和Native元件通訊。這個Native元件包括native的庫和自定義檢視,我們今天主要設計的內容是native庫方面的只是。自定義檢視的使用會在後面講到。

坑是什麼樣的坑

主要的是遇到一個業務需求,需要檢測當前應用的版本是什麼。需要返回當前的版本號和build數。

主要的需求在native來說非常簡單:

    NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
    NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];

兩句分別獲得了版本號和build數。

開始填坑

填坑其實也是意外的簡單。當然,我們不準備把這個程式碼作為庫釋出到npm上給別人用,所以複雜度自然降低了不少。

首先、在Xcode裡建立RNUpgrade類作為後面和RN通訊的native元件。這會在專案裡建立兩個objc的檔案RNUpgrade.hRNUpgrade.m

RNUpgrade.h標頭檔案中,新增RCTBridgeModule協議。要給RN暴露介面這個協議是必須的。

#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"

@interface RNUpgrade : NSObject<RCTBridgeModule>

@end

之後對於標頭檔案就可以什麼都不用管了。至少對於暴露介面這件事是這樣的。

下面就來看原始檔吧。

看文件,要暴露native方法就必須在原始檔裡包含一個巨集的呼叫,這個巨集是:RCT_EXPORT_MODULE()。這個巨集可以包含一個引數指定RN中訪問這個模組的名字。預設的就是你的objc類的名字。

#import "RNUpgrade.h"
#import "RCTUtils.h"
#import "AppDelegate.h"

NSString *const RNUPGRADE_ERROR_DOMAIN = @"Upgrade info error";

@implementation RNUpgrade

RCT_EXPORT_MODULE();

@end

那麼如何來暴露出一個方法呢?使用RCT_EXPORT_METHOD()巨集。官網的例子:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{ 
    RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}

巨集RCT_EXPORT_METHOD的引數就是這個方法的宣告部分,方法體在外面。RCT_EXPORT_METHOD(someMethod:(NSString*)stringParameter)這樣的,然後外面寫方法體。
那麼,我要返回現在APP的版本資訊就可以寫成這樣:

RCT_EXPORT_METHOD(getCurrentInfo) {
  @try {
    NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
    NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
    return @{@"versionName": version, @"versionCode": build}
  } @catch (NSException *exception) {
      //Log error info...
  }
}

但是,如何返回字典呢?直接return?接著差文件。

暴露給RN的方法是不能直接返回任何東西的。因為RN的呼叫時非同步的,所以只能使用回撥的方式,或者觸發事件的方式實現返回值。

回撥!看個官網的例子:

RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{ 
    NSArray *events = ... 
    callback(@[[NSNull null], events]);
}

好的,回撥就說到這裡了。因為筆者的專案已經上了async/await了,回撥就顯得沒啥必要了。而且,文件顯示。RN也提供了暴露介面返回Promise的支援。只需要在方法裡接受兩個引數,一個resolver,一個rejecter

RCT_EXPORT_METHOD(getCurrentInfo:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  @try {
    NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
    NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
    resolve(@{@"versionName": version, @"versionCode": build});
  } @catch (NSException *exception) {
    NSError *error = [NSError errorWithDomain:RNUPGRADE_ERROR_DOMAIN code:1 userInfo: exception.userInfo];
    reject(exception.name, exception.reason, error);
  }
}

於是,這樣就可以返回一個Promise了。

在RN的專案裡呼叫這個方法:

// 首先通過`NativeModules`接收暴露的native模組。
import { NativeModules } from "react-native"
const upgrade = NativeModules.RNUpgrade

// 方法呼叫
const ret = await Promise.all([upgrade.getCurrentInfo(), upgrade.getUpgradeInfo()])

沒錯,模組還有另外一個native方法。這個native方法也返回一個Promise

返回宣告相同的native方法

其實在native模組裡很多方法的宣告都是一模一樣的:resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject。因為我只需要接收一個resolverrejecter以便返回一個Promise。於是就用到了RN提供的另外一個巨集:RCT_REMAP_METHOD。這個巨集專門用來處理宣告基本一樣的情況。它會把native裡的宣告基本一樣的巨集對映到一個唯一的RN方法名稱上。

RCT_REMAP_METHOD(getCurrentInfo,
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  @try {
    NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
    NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
    resolve(@{@"versionName": version, @"versionCode": build});
  } @catch (NSException *exception) {
    NSError *error = [NSError errorWithDomain:RNUPGRADE_ERROR_DOMAIN code:1 userInfo: exception.userInfo];
    reject(exception.name, exception.reason, error);
  }
}

基本上在專案裡如何暴露一個native方法給RN的js呼叫非常簡單,就如上面所述一樣。

  1. 在標頭檔案裡繼承了RCTBridgeModule協議。
  2. 在原始檔裡使用RCT_EXPORT_MODULE();巨集。
  3. 使用巨集RCT_EXPORT_METHOD暴露方法。
    如果方法需要返回值的話使用回撥、或者Promise。這也只是native方法寫幾個引數的問題。

重要的一點:執行緒

在文件中有這麼一點:多執行緒。千萬不要根據RN實現的一些細節就假設你的模組執行在某某執行緒上。官網也說了,這個是會變的。如果你要確定你的程式碼執行在什麼執行緒上,通過方法- (dispatch_queue_t)methodQueue來指定。

注意:指定的methodQueue會被你模組裡的所有方法共享。

如果執行在主執行緒上:

- (dispatch_queue_t)methodQueue
{ 
    return dispatch_get_main_queue();
}

如果執行在自己建立的執行緒上:

- (dispatch_queue_t)methodQueue
{ 
    return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}

如果模組裡只有小部分程式碼執行在其他的執行緒上,可以使用native裡傳統的方法dispatch_async來實現:

RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 在這裡執行長時間的操作 ... 
        // 你可以在任何執行緒/佇列中執行回撥函式 
        callback(@[...]);
    });
}

而且:methodQueue
方法會在模組被初始化的時候被執行一次,然後會被React Native的橋接機制儲存下來,所以你不需要自己儲存佇列的引用

省心省力!

填坑完畢!

歡迎加群互相學習,共同進步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,轉載請註明出處!

本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sunshine-anycall/p/6141372.html,如需轉載請自行聯絡原作者


相關文章