此文写于2016/03/14,所以比较老了
React-Native 中的 宏
RCT_CONACT
/**
* Concat two literals. Supports macro expansions,
* e.g. RCT_CONCAT(foo, __FILE__).
*/
#define RCT_CONCAT2(A, B) A ## B
#define RCT_CONCAT(A, B) RCT_CONCAT2(A, B)
如 char* RCT_CONCAT(name, __FUNCTION__) = "logtag";
展开之后:
char* name__FUNCTION__ = "logtag";
RCT_EXTERN_REMAP_METHOD
定义在 (React/Base/RCTBridgeModule.h)
#define RCT_EXTERN_REMAP_METHOD(js_name, method) \
+ (NSArray<NSString *> *)RCT_CONCAT(__rct_export__, \
RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
return @[@#js_name, @#method]; \
}
用一放在类实现中:
@interface MyMacroDecl : NSObject
@end
@implementation MyMacroDecl
RCT_EXTERN_REMAP_METHOD(itemAtIndex,itemAtIndexPath:(NSIndexPath*)indexPath)
@end
上面的宏展开之后:
+ (NSArray<NSString *> *)__rct_export__itemAtIndex600 {
return @[@"itemAtIndex", @"itemAtIndexPath:(NSIndexPath*)indexPath"];
}
得到一个将要公司的方法的配置元组. 元组第一个是在JS 中调用所指定的名称,右边是对应的 ObjC 对应方法的签名.
我有种感觉就是, React 通过宏,将软件语义中需要通过协议来实现的方法使用 宏来帮且实现.
在 React 的库中,并不会直接使用 RCT_EXTERN_REMAP_METHOD
因为上面只是生成了 JS 中的方法名对 OC 中的方法名的映射的一个元组.
实际上使用的是另外的包装了 OC 方法签名的宏
PS: 上面的宏中 __COUNTER__
宏的使用也很关键. 参考 https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
它保证了生成的方法签名的唯一性.因为 __COUNTER__
是由编译器提供的一个计数器.
RCT_REMAP_METHOD
它在 RCT_EXTERN_REMARP_METHOD
基础之上加上了,OC 中的方法签名声明.
#define RCT_REMAP_METHOD(js_name, method) \
RCT_EXTERN_REMAP_METHOD(js_name, method) \
- (void)method
这样使用:
@implementation MyMacroDecl
RCT_REMAP_METHOD(itemAtIndex,itemAtIndexPath:(NSIndexPath*)indexPath){
}
@end
展开之后得到:
@implementation MyMacroDecl
+ (NSArray<NSString *> *)__rct_export__itemAtIndex600 {
return @[@"itemAtIndex", @"itemAtIndexPath:(NSIndexPath*)indexPath"];
}
- (void)itemAtIndexPath:(NSIndexPath*)indexPath{
}
@end
这样就实现了通过使用宏包装,自动为 Objc 的方法itemAtIndexPath:(NSIndexPath*)indexPath
添加到发布到 JS 环境的映射.
React 还提供了一个更方便的,它甚至可以自动确定发布到 JS 环境中的方法名.
以前的 ReactNative 按
RCT_EXPORT_METHOD
它是通过对 RCT_REMAP_METHOD
进一步包装,默认将 js_name
这一宏参数设置为空来实现的.
```objc
#define RCT_EXPORT_METHOD(method)
RCT_REMAP_METHOD(, method)
官方示例文档说明:
```objc
/*
* For example, in ModuleName.m:
*
* - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b
* { ... }
*
* becomes
*
* RCT_EXPORT_METHOD(doSomething:(NSString *)aString
* withA:(NSInteger)a
* andB:(NSInteger)b)
* { ... }
*
* and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
*/
对于如何实现一个带有 Promise 的方法,其文档说明如下:
/*
* ## Promises
*
* Bridge modules can also define methods that are exported to JavaScript as
* methods that return a Promise, and are compatible with JS async functions.
*
* Declare the last two parameters of your native method to be a resolver block
* and a rejecter block. The resolver block must precede the rejecter block.
*
* For example:
*
* RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString
* resolver:(RCTPromiseResolveBlock)resolve
* rejecter:(RCTPromiseRejectBlock)reject
* { ... }
*
* Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from
* JavaScript will return a promise that is resolved or rejected when your
* native method implementation calls the respective block.
*/
刚才忘记说一点了 就是我们公开的方法是无法直接返回值的,
必须得通过回调机制来返回值.
PS:我曾经实现过从公司实现接口直接给 JS 环境可以直接有返回值的方法,
实现方法就是: 直接用纯JS 注入一个同步方法. 这样的方法其实只能返回固定的值了.
RCT_EXPORT_MODULE(js_name)
其定义如下: (在 React/Base/RCTBridgeModule.h) 中
#define RCT_EXTERN extern __attribute__((visibility("default")))
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
展开之后得到:
extern __attribute__((visibility("default"))) void RCTRegisterModule(Class);
+ (NSString *)moduleName { return @""; }
+ (void)load { RCTRegisterModule(self); }
需要参与到交到的模块中,在其实现块中,使用 上面的宏.
上面的宏 实现的模型的基本要求.
+(void)load
在类被加载后调用以便自行注册到模块表中.
其代码注释:
/**
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_name argument
* will be used as the JS module name. If omitted, the JS module name will
* match the Objective-C class name.
*/
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName;
�### RCT_EXTERN_REMAP_MODULE
,RCT_EXTERN_MODULE
和 RCT_EXTERN_METHOD
这几个宏的定义如下, 主要是用来将 Swift 的类,或者 私有类的方法公开出去.
#define RCT_EXTERN_METHOD(method) \
RCT_EXTERN_REMAP_METHOD(, method)
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
objc_name : objc_supername \
@end \
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE(js_name)
#define RCT_EXTERN_MODULE(objc_name, objc_supername) \
RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
上面是通过宏创建一个空的类声明:
@interface objc_name : objc_supername \
@end \
一个实现了 RCTBridgeModule
协议,名为 RCTExternModule
的空类别声明:
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
和一个类及类别的实现:
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE(js_name)
@end
还是通过 RCT_EXPORT_MODULE(js_name)
实现了 moduleName
这一 Getter
比如向 JS 环境发布 Swift 环境下的方法的方式就是在 OC 环境下创建一个用于发布方法的包装类.
比如下面就是发布 Swift 环境下的方法的官方文档 .
/**
* Use this macro in a private Objective-C implementation file to automatically
* register an external module with the bridge when it loads. This allows you to
* register Swift or private Objective-C classes with the bridge.
*
* For example if one wanted to export a Swift class to the bridge:
*
* MyModule.swift:
*
* @objc(MyModule) class MyModule: NSObject {
*
* @objc func doSomething(string: String! withFoo a: Int, bar b: Int) { ... }
*
* }
*
* MyModuleExport.m:
*
* #import "RCTBridgeModule.h"
*
* @interface RCT_EXTERN_MODULE(MyModule, NSObject)
*
* RCT_EXTERN_METHOD(doSomething:(NSString *)string withFoo:(NSInteger)a bar:(NSInteger)b)
*
* @end
*
* This will now expose MyModule and the method to JavaScript via
* `NativeModules.MyModule.doSomething`
*/
RCTBridgeModule
原生模块的协议.
RCTBridgeModule
协议
有1个必须的方法moduleName
,
有2个可选的属性 bridge
,methodQueue
有4个可选的方法 methodsToExport
,constantsToExport
,batchDidComplete
,partialBatchDidFlush
通过使用 RCTBridgeModule 头文件提供的宏,第一个必须的方法,都通过宏帮我们实现了.
其实则按使用场景实现.
下面对其协议属性及方法进行说明.
moduleName
模块名
默认是以类名为模块名. 可以通过指定的宏来实现指定模块名.
methodQueue
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
在 iOS 开发中通常我们都要求 UI 操作需要在 UI 线程中执行,一些 IO 操作或者网络操作在 工作线程中执行.
为了避免阻塞 UI 线程, 模块的操作默认都是在 默认的后台线程中执行的.
如果需要在 UI 线程中执行,
可以实现此可选的协议方法,如下:
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
这里是作为一个 Getter
方法来实现此属性协议,如果你只是需要用到此模块的后台线程.
那可以在类实现中,添加一个属性合成语句即可,模块初始化时会被自动设置属性的.
@synthesize methodQueue = _methodQueue;
bridge
@property (nonatomic, weak, readonly) RCTBridge *bridge;
当需要在模块中调用 JS 环境中代码时,就需要这个 bridge
了.
为了在模块中可以使用 bridge
实现,只要在类实现中添加一个相应的属性合成声明即可,
模型初始化时会自动设置此变量.
@synthesize bridge = _bridge;
关于 RCTBridge
后面会有更详细的研究.
methodsToExport
- (NSArray<id<RCTBridgeMethod>> *)methodsToExport;
通过实现此协议方法,可以提供一个批量公开模型方法的接口. 因此也可以通过此协议来返回要公开的接口.
使用宏的方法也是最终解析 方法映射表然后得到 实现了 RCTBridgeMethod
协议的类 RCTModuleMethod
值得注意的是,此协议方法只会在注册时被调用一次.
constantsToExport
- (NSDictionary<NSString *, id> *)constantsToExport;
通过此协议方法可以实现公开属性对对应的 JS 模块中.
比如公开一个名为 MyName
的属性.
那 JS 环境下就可以通过 NativeModules.ModuleName.MyName
的方式来使用此模块的属性.
值得注意的是,此协议方法只会在注册时被调用一次. 无法用来传递动态的值.
batchDidComplete
- (void)batchDidComplete;
通知此模块有一批JS 方法的调用已经完成了.
partialBatchDidFlush
- (void)partialBatchDidFlush;
通知此模块一些 JS 方法调用已经开始了. 它比 batchDidComplete
先调用,也会被更频繁的调用.
通信桥梁
/**
* A reference to the RCTBridge. Useful for modules that require access
* to bridge features, such as sending events or making JS calls. This
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add `@synthesize bridge = _bridge;`
*/
@property (nonatomic, weak, readonly) RCTBridge *bridge;
enqueueJSCall: args:
从通信上来说, RCTBridge
提供了如下方法:
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
用来调用当前 RCTBridge
连接的 JS 环境中的 JS 函数.
moduleDotMethod
正如其名,就是类似 React.getCompById
这样的调用方法名,
args
是调用的参数.
此方法在任何线程中调用都安全,因为 RCTBridge
总是会在 JS 线程中调用它的.
RCTBridgeDelegate
此协议为我们实现自己的加载JS bundle 逻辑提供了可能.
-
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge;
动态指定 JS Bundle 的 URL -
- (NSArray *)extraModulesForBridge:(RCTBridge *)bridge;
为此Bridge 指定额外的需要公开的原生模块. -
- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback;
完全的自定义加载 JS Bundle 代码逻辑