zoukankan      html  css  js  c++  java
  • 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', {  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', {  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', {  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', {  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
    ", __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 Code
  • 相关阅读:
    应用的可被点击与不可被点击
    去掉NavigationBar底部的黑线
    iOS 设计模式之工厂模式
    GCD的用法
    ARC以及MRC中setter方法
    alpha,hidden,opaque的一些认识
    self进行weak化
    iOS之获取当前时间日期并按固定格式显示
    iOS之AVPlayer的简单应用
    Xcode快捷键、小技巧与xib圆角设置
  • 原文地址:https://www.cnblogs.com/XYQ-208910/p/12202232.html
Copyright © 2011-2022 走看看