1、簡介react
在前面介紹了不少ReactNative中UI組件和API組件,這些都是Facebook團隊封裝好的基礎組件,開發者能夠直接使用。然而,在實際的開發過程當中,面對複雜的需求,此時原生的Native組件可能就沒法知足要求了。固然,這種狀況Facebook團隊是固然考慮過了,因此在ReactNative開發中也支持開發者進行自定義API組件。編程
2、詳解react-native
一、類模塊和方法:api
一個普通的OC類以及方法,並不會被系統處理成模塊進而被調用。模塊必須在編譯以及運行時向系統註冊,同時告訴系統什麼屬性和方法能夠被JavaScript調用。自定義的OC模塊類必須遵照RCTBridgeModule協議。RCTBridgeModule協議定義了一些模塊的基本屬性和方法以及一些宏命令。能夠直接經過宏命令來告訴ReactNative須要註冊的模塊類和暴露的方法。注意JavaScript沒法識別方法重載,因此定義方法時不要重名。 經常使用宏命令以下:數組
//一、註冊模塊類。可選的js_name參數將用做JS模塊名稱。若是省略,則JS模塊名稱將與Objective-C類名稱匹配。
//注意:若是定義的類名包含RCT前綴,會被系統格式化去除。例如原生的RCTActionSheetManager被格式化成ActionSheetManager
//也即:var RCTActionSheetManager = require('NativeModules').ActionSheetManager #define RCT_EXPORT_MODULE(js_name) //二、將OC中定義的模塊方法暴露出來,method是OC方法 #define RCT_EXPORT_METHOD(method) //三、將OC中定義的模塊方法暴露出來,method是OC方法,js_name是method的自定義名稱 #define RCT_REMAP_METHOD(js_name, method) //四、這種方式經過「 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的類型都是支持的。以下所示:app
//字符串類型 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
二、回調函數async
ReactNative中定義了幾種類型的塊函數做爲回調函數,RCTModuleMethod以及MessageQueue會根據不一樣的類型來做對應的處理。Native中定義的回調函數會在執行時都會將數據傳遞給JavaScript環境,來執行對應的JavaScript函數。定義的幾種回調函數以下所示:ide
//接收多個參數的回調函數,定義回調函數參數的順序要和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)
三、線程
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中返回共享實例便可。而不是建立一個相同標籤的實例。
四、常量導出
ReactNative還支持Native模塊暴露一些常量數據供JavaScript模塊方法使用。主要有這幾種用法:
(1)Native組件中的常量值,例如版本和事件名稱等;(2)Native中定義枚舉在JavaScript中使用的對應值;(3)邊界定義,例如控件容許的最小尺寸或者默認尺寸等。
常量的暴露經過方法constantsToExport方法實現,以下:
//一、OC中導出一個字典對象 -(NSDictionary *)constantsToExport { return @{@"name": @"Zhangsan"}; } //JavaScript中訪問以下, Module爲註冊的模塊類 Module.name //二、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);
五、事件
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
3、使用
一、類模塊
#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
//import { NativeModules } from 'react-native' let NativeModules = require('NativeModules'); //import 或 require 方式都行
//下面結果顯示了模塊名爲LoginManager,若是在註冊時傳入參數爲自定義模塊類名如MyLoginManager,則會顯示MyLoginManager,使用模塊類時也是MyLoginManager。 console.log(NativeModules);
二、模塊方法
#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
//導入模塊 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
三、回調函數
#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
//導入模塊 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、常量導出
#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
#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
//導入模塊 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
五、線程
#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
//導入模塊 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
六、事件
#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
//導入模塊 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'
4、完整代碼
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
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
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
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
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 */
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/> } }