1. 程式人生 > >ReactNative: 自定義ReactNative API元件

ReactNative: 自定義ReactNative API元件

一、簡介

在前面介紹了很多ReactNative中UI元件和API元件,這些都是Facebook團隊封裝好的基礎元件,開發者可以直接使用。然而,在實際的開發過程中,面對複雜的需求,此時原生的Native元件可能就無法滿足要求了。當然,這種情況Facebook團隊是當然考慮過了,所以在ReactNative開發中也支援開發者進行自定義API元件。

 

二、詳解

1、類模組和方法:

一個普通的OC類以及方法,並不會被系統處理成模組進而被呼叫。模組必須在編譯以及執行時向系統註冊,同時告訴系統什麼屬性和方法可以被JavaScript呼叫。自定義的OC模組類必須遵守RCTBridgeModule協議。RCTBridgeModule協議定義了一些模組的基本屬性和方法以及一些巨集命令。可以直接通過巨集命令來告訴ReactNative需要註冊的模組類和暴露的方法。注意JavaScript無法識別方法過載,所以定義方法時不要重名。 常用巨集命令如下:

//1、註冊模組類。可選的js_name引數將用作JS模組名稱。如果省略,則JS模組名稱將與Objective-C類名稱匹配。
//注意:如果定義的類名包含RCT字首,會被系統格式化去除。例如原生的RCTActionSheetManager被格式化成ActionSheetManager
//也即:var RCTActionSheetManager = require('NativeModules').ActionSheetManager #define RCT_EXPORT_MODULE(js_name) //2、將OC中定義的模組方法暴露出來,method是OC方法 #define RCT_EXPORT_METHOD(method) //3、將OC中定義的模組方法暴露出來,method是OC方法,js_name是method的自定義名稱 #define RCT_REMAP_METHOD(js_name, method) //4、這種方式通過“ NativeModules.MyModule.method”將MyModule和方法method同時公開給JavaScript。
//obj_name:模組類 objc_supername:模組類的父類 js_name: 模組類的別名 #define RCT_EXTERN_MODULE(objc_name, objc_supername) #define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername)

對了模組類的註冊,如果沒有不用巨集定義匯出,也可以實現下面這個協議方法即可,它其實在巨集定義RCT_EXPORT_MODULE中被實現。

// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName

JavaScript和Objective-C是兩個完全不同的語言,所支援的資料型別也各有不同,如果之間需要通訊,那必須完成資料型別的轉換。ReactNative中雙方的通訊資料採用JSON型別來轉換,因此支援標準JSON的型別都是支援的。如下所示:

//字串型別
string (NSString)

//數值型別
number (NSInteger, float, double, CGFloat, NSNumber)

//布林型別
boolean (BOOL, NSNumber)

//陣列型別
array (NSArray)

//字典型別
map (NSDictionary)

//block型別
function (RCTResponseSenderBlock) 

執行Native模組方法之前,RCTModuleMethod會根據Native方法定義的引數型別通過RCTConvert.h進行轉換。在RCTConvert.h中,除了支援JSON標準型別外,也支援一系列常用型別。如下所示:

//這些型別都包括但不侷限於一下型別(可以檢視類RCTConvert.h)
NSDate、UIColor、UIFont、NSURL、NSURLRequest、UIImage....
UIColorArray、NSNumberArray、NSURLArray...
NSTextAlignment、NSUnderlineStyle....
CGPoint、CGSize....

//例如JavaScript中的date轉換成OC的NSDate
date.toTime()    =>   NSDate

2、回撥函式

ReactNative中定義了幾種型別的塊函式作為回撥函式,RCTModuleMethod以及MessageQueue會根據不同的型別來作對應的處理。Native中定義的回撥函式會在執行時都會將資料傳遞給JavaScript環境,來執行對應的JavaScript函式。定義的幾種回撥函式如下所示:

//接收多個引數的回撥函式,定義回撥函式引數的順序要和Native模組中傳入的NSArray中的物件順序保持一致,這樣才能接收到正確的引數
typedef void (^RCTResponseSenderBlock)(NSArray *response)

//接收錯誤引數的回撥函式
typedef void (^RCTResponseErrorBlock)(NSError *error)

//處理Promise Resolve的非同步回撥函式,支援then.函數語言程式設計
typedef void (^RCTPromiseResolveBlock)(id result)

//處理Promise Reject的非同步回撥函式,支援then.函數語言程式設計
typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error)

3、執行緒

JavaScript程式碼都是單執行緒執行的,而在Native模組中,執行緒問題自然而然需要被關注。在ReactNative中,所有的Native模組都預設在各自獨立的GCD序列佇列上。如果需要特別指定某個執行緒佇列,可以通過-(dispatch_queue_t)methodQueue方法來實現,如下:

//給某一個非同步執行緒自定義佇列
-(dispatch_queue_t)methodQueue{
    return dispatch_queue_create("com.facebook.ReactNative.NameQueue", DISPATCH_QUEUE_SERIAL);
} 

模組中所有的模組方法都會執行在同一執行緒佇列中,如果某些方法需要單獨指定佇列,可以使用dispatch_async函式實現,如下:

//在thread方法內非同步執行(都是非同步執行緒,但是佇列不同)
RCT_EXPORT_METHOD(thread:(BOOL)newQueue{
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                            code excute...
                        });
                    }
                 )

注意:如果多個模組需要共享一個執行緒佇列,那麼需要手動快取共享的佇列例項,並在methodQueue中返回共享例項即可。而不是建立一個相同標籤的例項。

4、常量匯出

ReactNative還支援Native模組暴露一些常量資料供JavaScript模組方法使用。主要有這幾種用法:

(1)Native元件中的常量值,例如版本和事件名稱等;(2)Native中定義列舉在JavaScript中使用的對應值;(3)邊界定義,例如控制元件允許的最小尺寸或者預設尺寸等。

常量的暴露通過方法constantsToExport方法實現,如下:

//1、OC中匯出一個字典物件
-(NSDictionary *)constantsToExport {
    return @{@"name": @"Zhangsan"};  
}
//JavaScript中訪問如下, Module為註冊的模組類
Module.name


//2、OC中定義一個列舉並匯出
typedef NS_ENUM(NSInteger, MoveAnimation){
    MoveAnimationNome,
    MoveAnimationFade,
    MoveAnimationSlide
}
-(NSDictionary *)constantsToExport{
  return @{
      @"MoveAnimationNome": @(MoveAnimationNome),
      @"MoveAnimationFade": @(MoveAnimationFade),
      @"MoveAnimationSlide": @(MoveAnimationSlide),
  }
}

//給RCTConvert類新增擴充套件,這樣在模組方法呼叫中使用常量匯出的列舉值,通訊到Native中時,會從整型自動轉換為定義的列舉型別
@implementation RCTConvert (MoveAnimation)
RCT_ENUM_CONVERTER(MoveAnimation,
                   (@{
                      @"MoveAnimationNome": @(MoveAnimationNome),
                      @"MoveAnimationFade": @(MoveAnimationFade),
                      @"MoveAnimationSlide": @(MoveAnimationSlide),
                   }), MoveAnimationNome, integerValue)
@end

//JavaScript中訪問如下
Module.updateMoveAnimation(Module.MoveAnimationSlide, callback);

5、事件

ReactNative在Native向JavaScript傳遞訊息機制的基礎上實現了一個非常低耦合的訊息事件訂閱系統,Native通過RCTEventDispatcher向JavaScript端的EventEmitter模組傳送事件訊息,由EventEmitter模組通知該事件的訂閱者來執行事件的響應。在大多數場景下,只需要使用這種通知的方式間接完成Native對JavaScript的呼叫。如下:

//首先在JavaScript端對事件進行訂閱,並且新增事件響應函式
const { NativeAppEventEmitter } = require('react-native');
let subscription = NativeAppEventEmitter.addListener('EventReminder', (reminder) => console.log(reminder.name) )

//在OC中,當在Native模組上發出事件通知時,EventEmitter模組則會執行所有註冊EventReminder事件的響應函式
#import "RCTEventDispacther.h"
[self.bridge.eventDispacther sendAppEventWithName: @"EventReminder" body:@{@"name": eventName}]

ReactNative中定義了不同的介面以及接收者來區分事件的型別,注意在合適的時候需要手動取消事件的訂閱,以避免記憶體洩露,型別如下所示:

//傳送應用相關的事件,例如資料更新
//NativeAppEventEmitter
-(void)sendAppEventWithName:(NSString *)name body:(id)body

//傳送裝置相關的事件,例如地理位置和螢幕旋轉
//DeviceEventEmitter
-(void)sendDeviceEventWithName:(NSString *)name body:(id)body

 

三、使用

1、類模組

  • 首先在建立OC類,然後使用巨集定義註冊
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

//必須要實現RCTBridgeModule協議 @interface LoginManager : NSObject<RCTBridgeModule> @end NS_ASSUME_NONNULL_END
#import "LoginManager.h"

@implementation LoginManager

//下面的方式二選一,不可同時使用,不然就重複了,編譯報錯
//方式一 不傳引數:匯出模組類:預設名稱為當前類名 LoginManager <===> RCT_EXPORT_MOUDLE(LoginManager) RCT_EXPORT_MODULE(); //方式二 傳引數:匯出模組類:設定自定義的名稱為 MyLoginManager //RCT_EXPORT_MODULE(MyLoginManager); @end
  • 然後在ReactNative中匯入,列印看模組類是否註冊成功,如下所示:
//import { NativeModules } from 'react-native'
let NativeModules = require('NativeModules');

//import 或 require 方式都行
//下面結果顯示了模組名為LoginManager,如果在註冊時傳入引數為自定義模組類名如MyLoginManager,則會顯示MyLoginManager,使用模組類時也是MyLoginManager。 console.log(NativeModules);

 

2、模組方法

  • 首先在OC類中定義方法,然後使用巨集定義暴露給模組
#import "LoginManager.h"

@implementation LoginManager

//匯出模組類
RCT_EXPORT_MODULE();

//重對映,auth為code方法的新名稱
RCT_REMAP_METHOD(auth,code:(NSString *)account{
                      NSLog(@"%s---獲取驗證----",__func__);
                      NSLog(@"account----%@",account);
                  });

//login為方法名
RCT_EXPORT_METHOD(login:(NSString *)account password:(NSString *)password{
                      NSLog(@"%s---登入賬號----",__func__);
                      NSLog(@"account----%@",account);
                      NSLog(@"password----%@",password);
                  });

//logout為方法名
RCT_EXPORT_METHOD(logout:(NSString *)account{
                      NSLog(@"%s---退出賬號----",__func__);
                      NSLog(@"account----%@",account);
                  });
@end
  • 然後在ReactNative中,列印看模組類是否註冊成功並呼叫,如下所示:
//匯入模組
import { NativeModules } from 'react-native';

//模組類
const LoginManager = NativeModules.LoginManager;

//列印模組類
console.log("LoginManager",LoginManager);

//呼叫模組類的方法
LoginManager.auth("xiayuanquan");
LoginManager.login("xiayuanquan", "123456");
LoginManager.logout("xiayuanquan");
2020-01-17 14:22:21.453 [info][tid:com.facebook.react.JavaScript] 'LoginManager', { auth: { [Function: fn] type: 'async' },
  login: { [Function: fn] type: 'async' },
  logout: { [Function: fn] type: 'async' } }

[14:21:51] -[LoginManager code:] [第19行] -[LoginManager code:]---獲取驗證----
[14:21:51] -[LoginManager code:] [第20行] account----xiayuanquan

[14:21:51] -[LoginManager login:password:] [第25行] -[LoginManager login:password:]---登入賬號----
[14:21:51] -[LoginManager login:password:] [第26行] account----xiayuanquan
[14:21:51] -[LoginManager login:password:] [第27行] password----123456

[14:21:51] -[LoginManager logout:] [第32行] -[LoginManager logout:]---退出賬號----
[14:21:51] -[LoginManager logout:] [第33行] account----xiayuanquan

 

3、回撥函式

  • 在OC中定義帶回調函式的方法
#import "LoginManager.h"

@implementation LoginManager

//匯出模組類
RCT_EXPORT_MODULE();

//設定普通的回撥函式
//fetchUserInfoWithToken為方法名,successCallback為成功回撥,failureCallback為失敗回撥
RCT_EXPORT_METHOD(fetchUserInfoWithToken:(NSString *)token success:(RCTResponseSenderBlock)successCallback failure:(RCTResponseErrorBlock)failureCallback
    {
            if(token.length>0 && successCallback){
                  successCallback(@[
                                    @"account = xiayuanquan",
                                    @"password = 123456",
                                    [NSString stringWithFormat:@"token = %@",token]
                                  ]);
            }
            else{
                  if(failureCallback){
                        failureCallback(
                                [[NSError alloc] initWithDomain:NSOSStatusErrorDomain
                                                           code:404
                                                       userInfo: @{NSLocalizedDescriptionKey: @"token exception"}]
                                        );
                  }
            }
    });


//設定非同步處理的回撥函式
//sendMessage為方法名,successCallback為成功回撥,failureCallback為失敗回撥
RCT_EXPORT_METHOD(sendMessage:(NSString *)message success:(RCTPromiseResolveBlock)successCallback failure:(RCTPromiseRejectBlock)failureCallback
    {
            if(message.length>0 && successCallback){
                  successCallback(@"傳送成功!");
            }
            else{
                  if(failureCallback){
                      failureCallback(@"300",@"傳送失敗",nil);
                  }
            }
    });

@end
  • 在ReactNative中,呼叫該函式,列印回撥函式結果
//匯入模組
import { NativeModules } from 'react-native';

//模組類
const LoginManager = NativeModules.LoginManager;

//接收普通回撥函式
//傳入正確的token,觸發成功回撥
LoginManager.fetchUserInfoWithToken("xyakajsd121jdsjd", (account, password, token)=>{
    console.log(account + ", " +password + ", "+token);
}, (error) => {
    console.log("error code: " +error.code);
    Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
});

//傳入錯誤的token,觸發失敗回撥
LoginManager.fetchUserInfoWithToken("", (account, password, token)=>{
    console.log(account + ", " +password + ", "+token);
}, (error) => {
    console.log("error code: " +error.code);
    Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
});

//---------------------------------------------------------------------------------------//

//處理Promise回撥函式
//傳送訊息成功,觸發成功回撥
LoginManager.sendMessage("Hello world")
    .then((message) => { console.log("message-----"+message) })
    .catch((error) => { console.log("error----"+error.code+", " + error.message) });

//傳送訊息失敗,觸發成功回撥
LoginManager.sendMessage("")
    .then((message) => { console.log("message-----"+message) })
    .catch((error) => { console.log("error----"+error.code +", " + error.message) });
2020-01-17 15:30:03.594 [info][tid:com.facebook.react.JavaScript] account = xiayuanquan, password = 123456, token = xyakajsd121jdsjd
2020-01-17 15:30:03.599 [info][tid:com.facebook.react.JavaScript] error code: ENSOSSTATUSERRORDOMAIN404
2020-01-17 15:30:03.600 [info][tid:com.facebook.react.JavaScript] 'NSLocalizedDescription', 'token exception'

2020-01-17 15:30:03.603 [info][tid:com.facebook.react.JavaScript] message-----傳送成功!
2020-01-17 15:30:03.611 [info][tid:com.facebook.react.JavaScript] error----300, 傳送失敗

  

4、常量匯出

  • 在OC中定義常量並匯出
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

//OC中定義一個列舉常量
typedef NS_ENUM(NSInteger, MoveDiretion){
    MoveDiretionNone,
    MoveDiretionLeft,
    MoveDiretionRight,
    MoveDiretionBottom,
    MoveDiretionTop
};

@interface LoginManager : NSObject<RCTBridgeModule>

@end
NS_ASSUME_NONNULL_END
#import "LoginManager.h"

@implementation LoginManager

//匯出模組類
RCT_EXPORT_MODULE();

//重寫constantsToExport, 列舉常量匯出
- (NSDictionary<NSString *, id> *)constantsToExport {
   return @{
        @"MoveDiretionNone": @(MoveDiretionNone),
        @"MoveDiretionLeft": @(MoveDiretionLeft),
        @"MoveDiretionRight": @(MoveDiretionRight),
        @"MoveDiretionBottom": @(MoveDiretionBottom),
        @"MoveDiretionTop": @(MoveDiretionTop)
   };
}

//定義一個移動方法,根據傳入的列舉值移動
//move為方法名
RCT_EXPORT_METHOD(move:(MoveDiretion)moveDiretion{
                    switch(moveDiretion){
                        case MoveDiretionNone:
                             NSLog(@"仍保持原始位置 --- MoveDiretionNome");
                        break;
                        case MoveDiretionLeft:
                             NSLog(@"向左邊移動位置 --- MoveDiretionLeft");
                        break;
                        case MoveDiretionRight:
                             NSLog(@"向右邊移動位置 --- MoveDiretionRight");
                        break;
                        case MoveDiretionBottom:
                             NSLog(@"向下邊移動位置 --- MoveDiretionBottom");
                        break;
                        case MoveDiretionTop:
                             NSLog(@"向上邊移動位置 --- MoveDiretionTop");
                        break;
                    }
                 });

@end
  • 給RCTConvert建立分類,進行型別轉換
#import "RCTConvert+MoveDiretion.h"
#import "LoginManager.h"

@implementation RCTConvert (MoveDiretion)

//給RCTConvert類新增擴充套件,這樣在模組方法呼叫中使用常量匯出的列舉值,通訊到Native中時,會從整型自動轉換為定義的列舉型別
RCT_ENUM_CONVERTER(MoveDiretion,(@{
   @"MoveDiretionNone": @(MoveDiretionNone),
   @"MoveDiretionLeft": @(MoveDiretionLeft),
   @"MoveDiretionRight": @(MoveDiretionRight),
   @"MoveDiretionBottom": @(MoveDiretionBottom),
   @"MoveDiretionTop": @(MoveDiretionTop),
}), MoveDiretionNone, integerValue)

@end
  • 在ReactNative中,訪問常量,列印結果
//匯入模組
import { NativeModules } from 'react-native';

//模組類
const LoginManager = NativeModules.LoginManager;

//訪問模組常量
LoginManager.move(LoginManager.MoveDiretionNone);
LoginManager.move(LoginManager.MoveDiretionLeft);
LoginManager.move(LoginManager.MoveDiretionRight);
LoginManager.move(LoginManager.MoveDiretionBottom);
LoginManager.move(LoginManager.MoveDiretionTop);
[16:13:53] -[LoginManager move:] [第90行] 仍保持原始位置 --- MoveDiretionNome
[16:13:53] -[LoginManager move:] [第93行] 向左邊移動位置 --- MoveDiretionLeft
[16:13:53] -[LoginManager move:] [第96行] 向右邊移動位置 --- MoveDiretionRight
[16:13:53] -[LoginManager move:] [第99行] 向下邊移動位置 --- MoveDiretionBottom
[16:13:53] -[LoginManager move:] [第102行] 向上邊移動位置 --- MoveDiretionTop

 

5、執行緒

  • 在OC中重寫methodQueue協議方法,給當前模組類指定自定義的序列佇列
#import "LoginManager.h"

@implementation LoginManager

//匯出模組類
RCT_EXPORT_MODULE();

//可以重寫佇列,給當前模組類指定自定義的序列佇列。若不指定,則系統預設會給當前模組類隨機分配一個序列佇列。
//該方法重寫後,當前模組類的所有方法都會在這個自定義的序列佇列中非同步執行。除非開發者在方法體內手動切換其他執行緒。
-(dispatch_queue_t)methodQueue{ return dispatch_queue_create("com.facebook.ReactNative.LoginManagerQueue", DISPATCH_QUEUE_SERIAL); } //定義一個方法,獲取執行緒和佇列資訊 //thread為方法名 RCT_EXPORT_METHOD(thread:(BOOL)newQueue{ const char *queueName = dispatch_queue_get_label([self methodQueue]); NSLog(@"當前執行緒1 ------- %@-----%s", [NSThread currentThread], queueName); if(newQueue){ dispatch_async(dispatch_get_main_queue(), ^{ const char *queueName2 = dispatch_queue_get_label(dispatch_get_main_queue()); NSLog(@"當前執行緒2 ------- %@ -------%s", [NSThread currentThread], queueName2); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ const char *queueName3 = dispatch_queue_get_label(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); NSLog(@"當前執行緒3 ------- %@-------%s", [NSThread currentThread], queueName3); }); } }); @end
  • 在ReactNative中,訪問執行緒和佇列名稱,列印結果(name為null均為子執行緒,也即非同步) 
//匯入模組
import { NativeModules } from 'react-native';

//模組類
const LoginManager = NativeModules.LoginManager;

//訪問當前執行緒
LoginManager.thread(true);
[17:10:08] -[LoginManager thread:] [第118行] 當前執行緒1 ------- <NSThread: 0x281e3a0c0>{number = 8, name = (null)}-----com.facebook.ReactNative.LoginManagerQueue

[17:10:08] -[LoginManager thread:]_block_invoke [第124行] 當前執行緒2 ------- <NSThread: 0x281357f00>{number = 1, name = main} -------com.apple.main-thread

[17:10:08] -[LoginManager thread:]_block_invoke_2 [第129行] 當前執行緒3 ------- <NSThread: 0x281e3a0c0>{number = 8, name = (null)}-------com.apple.root.default-qos

 

6、事件

  • 在OC中新增監聽事件,監控螢幕旋轉狀態 
#import "LoginManager.h"

@implementation LoginManager

//匯出模組類
RCT_EXPORT_MODULE();

//初始化, 新增螢幕旋轉監聽者
-(instancetype)init {
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self 
                           selector:@selector(orientationDidChange:)
                            name:UIDeviceOrientationDidChangeNotification object:nil]; } return self; } //獲取當前螢幕的尺寸 static NSDictionary *Dimensions(){ CGFloat width = MIN(RCTScreenSize().width, RCTScreenSize().height); CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height); CGFloat scale = RCTScreenScale(); if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){ width = MAX(RCTScreenSize().width, RCTScreenSize().height); height = MIN(RCTScreenSize().width, RCTScreenSize().height); } return @{ @"width": @(width), @"height": @(height), @"scale": @(scale) }; } //定義一個方法,獲取螢幕資訊 //getDimensions為方法名 RCT_EXPORT_METHOD(getDimensions:(RCTResponseSenderBlock)callback{ if (callback) { callback(@[[NSNull null], Dimensions()]); } }); //監聽方法,使用RCTEventDispatcher的eventDispatcher呼叫sendDeviceEventWithName函式傳送事件資訊 //可以將事件名稱作為常量匯出,提供給ReactNative中使用,也即新增到constantsToExport方法中的字典中即可。類似上面的列舉。 @synthesize bridge = _bridge; -(void)orientationDidChange:(NSNotification *)notification { [_bridge.eventDispatcher sendDeviceEventWithName:@"orientationDidChange" body:@{ @"orientation": UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation) ? @"Landscape": @"Portrait", @"Dimensions": Dimensions()} ]; } //移除監聽者 -(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end
  • 在ReactNative中,新增事件訂閱,獲取螢幕寬度方向資訊
//匯入模組
import { NativeModules } from 'react-native';

//模組類
const LoginManager = NativeModules.LoginManager;

//訪問螢幕資訊
LoginManager.getDimensions( (error, dimensions) => {
    Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
});

//訂閱事件,監聽螢幕旋轉
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
let subscription = RCTDeviceEventEmitter.addListener('orientationDidChange', (dimensions) => {
    Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
});
//subscription.remove();
2020-01-17 18:20:03.234 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 414, scale: 3, height: 736 }
2020-01-17 18:20:03.234 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Portrait'
2020-01-17 18:20:03.234 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 414, scale: 3, height: 736 }
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Portrait'
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'width', 414
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'scale', 3
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'height', 736

2020-01-17 18:20:03.238 [info][tid:com.facebook.react.JavaScript] Running application "App" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF

2020-01-17 18:20:24.245 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 736, scale: 3, height: 414 }
2020-01-17 18:20:24.245 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Landscape'
2020-01-17 18:20:25.407 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 414, scale: 3, height: 736 }
2020-01-17 18:20:25.408 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Portrait'

 

四、完整程式碼

OC類:

LoginManager.h

//
//  LoginManager.h
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/16.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTUtils.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

//OC中定義一個列舉並匯出
typedef NS_ENUM(NSInteger, MoveDiretion){
    MoveDiretionNone,
    MoveDiretionLeft,
    MoveDiretionRight,
    MoveDiretionBottom,
    MoveDiretionTop
};

@interface LoginManager : NSObject<RCTBridgeModule>

@end

NS_ASSUME_NONNULL_END
View Code

LoginManager.m

//
//  LoginManager.m
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/16.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import "LoginManager.h"

@implementation LoginManager

//初始化, 新增螢幕旋轉監聽者
-(instancetype)init {
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
    }
    return self;
}

//匯出模組類
RCT_EXPORT_MODULE();


//重對映,auth為code方法的新名稱
RCT_REMAP_METHOD(auth,code:(NSString *)account{
                      NSLog(@"%s---獲取驗證----",__func__);
                      NSLog(@"account----%@",account);
                  });

//login為方法名
RCT_EXPORT_METHOD(login:(NSString *)account password:(NSString *)password{
                      NSLog(@"%s---登入賬號----",__func__);
                      NSLog(@"account----%@",account);
                      NSLog(@"password----%@",password);
                  });

//logout為方法名
RCT_EXPORT_METHOD(logout:(NSString *)account{
                      NSLog(@"%s---退出賬號----",__func__);
                      NSLog(@"account----%@",account);
                  });

//設定普通的回撥函式
//fetchUserInfoWithToken為方法名,successCallback為成功回撥,failureCallback為失敗回撥
RCT_EXPORT_METHOD(fetchUserInfoWithToken:(NSString *)token success:(RCTResponseSenderBlock)successCallback failure:(RCTResponseErrorBlock)failureCallback
    {
            if(token.length>0 && successCallback){
                  successCallback(@[
                                    @"account = xiayuanquan",
                                    @"password = 123456",
                                    [NSString stringWithFormat:@"token = %@",token]
                                  ]);
            }
            else{
                  if(failureCallback){
                        failureCallback(
                                          [[NSError alloc] initWithDomain:NSOSStatusErrorDomain
                                                                     code:404
                                                                 userInfo: @{NSLocalizedDescriptionKey: @"token exception"}]
                                        );
                  }
            }
    });


//設定非同步處理的回撥函式
//sendMessage為方法名,successCallback為成功回撥,failureCallback為失敗回撥
RCT_EXPORT_METHOD(sendMessage:(NSString *)message success:(RCTPromiseResolveBlock)successCallback failure:(RCTPromiseRejectBlock)failureCallback
    {
            if(message.length>0 && successCallback){
                  successCallback(@"傳送成功!");
            }
            else{
                  if(failureCallback){
                      failureCallback(@"300",@"傳送失敗",nil);
                  }
            }
    });


//重寫constantsToExport, 列舉常量匯出
- (NSDictionary<NSString *, id> *)constantsToExport {
   return @{
        @"MoveDiretionNone": @(MoveDiretionNone),
        @"MoveDiretionLeft": @(MoveDiretionLeft),
        @"MoveDiretionRight": @(MoveDiretionRight),
        @"MoveDiretionBottom": @(MoveDiretionBottom),
        @"MoveDiretionTop": @(MoveDiretionTop)
   };
}

//定義一個移動方法,根據傳入的列舉值移動
//move為方法名
RCT_EXPORT_METHOD(move:(MoveDiretion)moveDiretion{
                    switch(moveDiretion){
                        case MoveDiretionNone:
                             NSLog(@"仍保持原始位置 --- MoveDiretionNome");
                        break;
                        case MoveDiretionLeft:
                             NSLog(@"向左邊移動位置 --- MoveDiretionLeft");
                        break;
                        case MoveDiretionRight:
                             NSLog(@"向右邊移動位置 --- MoveDiretionRight");
                        break;
                        case MoveDiretionBottom:
                             NSLog(@"向下邊移動位置 --- MoveDiretionBottom");
                        break;
                        case MoveDiretionTop:
                             NSLog(@"向上邊移動位置 --- MoveDiretionTop");
                        break;
                    }
                 });


//可以重寫佇列,給當前模組類指定自定義的序列佇列。若不指定,則系統預設會給當前模組類隨機分配一個序列佇列。
//這個方法一旦重寫。當前模組的所有方法均會在該自定義的序列佇列中非同步執行
-(dispatch_queue_t)methodQueue{
    return dispatch_queue_create("com.facebook.ReactNative.LoginManagerQueue", DISPATCH_QUEUE_SERIAL);
}

//定義一個方法,獲取執行緒和佇列資訊
//thread為方法名
RCT_EXPORT_METHOD(thread:(BOOL)newQueue{
                  
              const char *queueName = dispatch_queue_get_label([self methodQueue]);
              NSLog(@"當前執行緒1 ------- %@-----%s", [NSThread currentThread], queueName);
              
              if(newQueue){
                  
                  dispatch_async(dispatch_get_main_queue(), ^{
                      const char *queueName2 = dispatch_queue_get_label(dispatch_get_main_queue());
                      NSLog(@"當前執行緒2 ------- %@ -------%s", [NSThread currentThread], queueName2);
                  });
          
                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                      const char *queueName3 = dispatch_queue_get_label(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
                      NSLog(@"當前執行緒3 ------- %@-------%s", [NSThread currentThread], queueName3);
                  });
              }
          });


//獲取當前螢幕的尺寸
static NSDictionary *Dimensions(){
    CGFloat width = MIN(RCTScreenSize().width, RCTScreenSize().height);
    CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
    CGFloat scale = RCTScreenScale();
    if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
        width = MAX(RCTScreenSize().width, RCTScreenSize().height);
        height = MIN(RCTScreenSize().width, RCTScreenSize().height);
    }
    return @{
        @"width": @(width),
        @"height": @(height),
        @"scale": @(scale)
    };
}
//定義一個方法,獲取螢幕資訊
//getDimensions為方法名
RCT_EXPORT_METHOD(getDimensions:(RCTResponseSenderBlock)callback{
    if (callback) {
        callback(@[[NSNull null], Dimensions()]);
    }
});

//監聽方法,使用RCTEventDispatcher的eventDispatcher呼叫sendDeviceEventWithName函式傳送事件資訊
//可以將事件名稱作為常量匯出,提供給ReactNative中使用,也即新增到constantsToExport方法中的字典中即可。類似上面的列舉。
@synthesize bridge = _bridge;
-(void)orientationDidChange:(NSNotification *)notification {
  [_bridge.eventDispatcher sendDeviceEventWithName:@"orientationDidChange" body:@{
      @"orientation": UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation) ? @"Landscape": @"Portrait",
      @"Dimensions": Dimensions()}
   ];
}

//移除監聽者
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end
View Code

RCTConvert+MoveDiretion.h

//
//  RCTConvert+MoveDiretion.h
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/17.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import <React/RCTConvert.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTConvert (MoveDiretion)

@end

NS_ASSUME_NONNULL_END
View Code

RCTConvert+MoveDiretion.m

//
//  RCTConvert+MoveDiretion.m
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/17.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import "RCTConvert+MoveDiretion.h"
#import "LoginManager.h"

@implementation RCTConvert (MoveDiretion)

//給RCTConvert類新增擴充套件,這樣在模組方法呼叫中使用常量匯出的列舉值,通訊到Native中時,會從整型自動轉換為定義的列舉型別
RCT_ENUM_CONVERTER(MoveDiretion,(@{
   @"MoveDiretionNone": @(MoveDiretionNone),
   @"MoveDiretionLeft": @(MoveDiretionLeft),
   @"MoveDiretionRight": @(MoveDiretionRight),
   @"MoveDiretionBottom": @(MoveDiretionBottom),
   @"MoveDiretionTop": @(MoveDiretionTop),
}), MoveDiretionNone, integerValue)

@end
View Code

PrefixHeader.pch

//
//  PrefixHeader.h
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/16.
//  Copyright © 2020 Facebook. All rights reserved.
//

#ifndef PrefixHeader_h
#define PrefixHeader_h

#ifdef DEBUG

#define NSLog(format, ...) printf("[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);

#else

#define NSLog(format, ...)

#endif

#endif /* PrefixHeader_h */
View Code

ReactNative中:

wrapper-rn-api.js

import React, { Component } from 'react';
import {
    View
} from 'react-native';

//匯入模組
import { NativeModules } from 'react-native';

//模組類
const LoginManager = NativeModules.LoginManager;

//列印模組類
console.log("LoginManager",LoginManager);

// 呼叫模組類的方法
LoginManager.auth("xiayuanquan");
LoginManager.login("xiayuanquan", "123456");
LoginManager.logout("xiayuanquan");

//接收普通回撥函式
LoginManager.fetchUserInfoWithToken("xyakajsd121jdsjd", (account, password, token)=>{
    console.log(account + ", " +password + ", "+token);
}, (error) => {
    console.log("error code: " +error.code);
    Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
});

LoginManager.fetchUserInfoWithToken("", (account, password, token)=>{
    console.log(account + ", " +password + ", "+token);
}, (error) => {
    console.log("error code: " +error.code);
    Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
});

//處理Promise回撥函式
LoginManager.sendMessage("Hello world")
    .then((message) => { console.log("message-----"+message) })
    .catch((error) => { console.log("error----"+error.code+", " + error.message) });

LoginManager.sendMessage("")
    .then((message) => { console.log("message-----"+message) })
    .catch((error) => { console.log("error----"+error.code +", " + error.message) });


//訪問模組常量
LoginManager.move(LoginManager.MoveDiretionNone);
LoginManager.move(LoginManager.MoveDiretionLeft);
LoginManager.move(LoginManager.MoveDiretionRight);
LoginManager.move(LoginManager.MoveDiretionBottom);
LoginManager.move(LoginManager.MoveDiretionTop);

//訪問當前執行緒
LoginManager.thread(true);

//事件
LoginManager.getDimensions( (error, dimensions) => {
    Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
});

//訂閱事件
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
let subscription = RCTDeviceEventEmitter.addListener('orientationDidChange', (dimensions) => {
    Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
});
//subscription.remove();

export default class CustomRNComponent extends Component{
    render (){
        return <View/>
    }
}
View Co