转载

iOS原生与RN的通信(Swift版)

因为实习的原因,已经好久没有写博客了。倒不是因为忙,而是因为每天都被业务代码填满,找不到很好的可以拿来写博客的素材。

我的公司是一家小公司,我一个人做Android开发,然后某天我技术主管让我学学React Native,顺带把iOS也接过去。

于是我踏上了一条不归路......

原来的项目是原生的iOS项目,在完全不懂iOS开发的情况下,靠着官方文档,以及各种教程,iOS接入也算是顺利。但是在原生与RN通信这块地方花了不少时间。因为原本公司的iOS项目是Swift写的,我们技术主管让我需要原生部分实现的地方都用Swift给他写(技术主管就是iOS开发)。

但是官网上的例子都是OC实现的,有关Swift的导出就一小部分介绍而已......网上的博客也是抄来抄去,毫无参考价值。

可能是我不懂iOS开发,也可能是因为我蠢,但为了这些和我一样蠢的人将来能少花点时间,我决定写这篇博客。

跳过各种如何接入啥啥的步骤,我们直接从iOS原生与RN如何通信开始(介绍不会全面,只介绍目前我项目中用的几种通信):

RN调用iOS原生方法

这部分官网是有教程的,也包括如何用Swift实现,步骤如下:

1:新建一个IOSIntentModule.h文件:

#import #import @interface IOSIntentModule : NSObject @end

2:新建一个IOSIntentModule.m文件:

#import "IOSIntentModule.h"
@implementation IOSIntentModule.h
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(test:(NSString *)name)
{
  NSLog(name);
}
@end

3:在RN中调用:

import { NativeModules } from 'react-native';
NativeModules. IOSIntentModule.test('Hello');

当你执行到“NativeModules. IOSIntentModule.test('Hello');” 这句的时候,就会去调用原生的test方法。然后会在控制台打印”Hello“。

“NativeModules”是RN里和原生通信的一个组件。“IOSIntentModule”是原生导出给RN调用的组件的名字,这个名字是在步骤2中,由“RCT_EXPORT_MODULE();” 这个宏指定的。

比如你这样写:“RCT_EXPORT_MODULE(iAmSoHandsome);” 。

那你就得这么调用:

“NativeModules. iAmSoHandsome.test('Hello');”。

如果你不写,那这个名字就和你的类名一样,在本例中就是“ IOSIntentModule”。

“RCT_EXPORT_METHOD()”这个宏是用来将原生的方法导出,只有用这个宏包裹的方法,才可以被RN调用。

那这样的话,其实是RN调用原生的OC方法了,如果我们想用Swift来写怎么办呢?

官网介绍,Swift不支持宏。所以我们还是得靠OC来当作一个桥梁,也就是RN——>OC——>Swift。RN调用原生的OC方法,然后OC在交给Swift去处理。具体步骤如下:

1:新建一个IOSIntentModule.swift文件:

@objc(IOSIntentModule)
class IOSIntentModule: NSObject {
  @objc func test(name: String) -> Void {
    print(name)
  }
}

2:修改一下我们的IOSIntentModule.m文件:

#import @interface RCT_EXTERN_MODULE(IOSIntentModule, NSObject)
RCT_EXTERN_METHOD(test:(NSString *)name)
@end

3:在桥接文件中引入RCTBridgeModule.h:

一旦你在项目混用OC和Swift两种语言,那就需要这个桥接文件。一般在项目的Supporting Files文件夹里(一般系统会自动生成的,叫做“项目名-Bridging-Header.h”,项目名就是你工程的项目名)。

在里面加上:

#import

然后你就可以在RN中通过“NativeModules. IOSIntentModule.test('Hello');”调用了。虽然看不懂iOS代码,但是我猜测应该是RN先调用OC的方法,然后OC再去调用Swift的方法。

这样你照猫画虎的多写几个方法,然后就可以愉快的用Swift调用啦。

RN调用iOS原生方法并回调

有时候我们并不想简单的调用方法,让他执行完毕就OK了这么简单。我们需要接收他们的返回值啥的,这个时候我们就需要一个回调。

比如进行图片选择的时候,需要RN调用原生的图片选择器,然后将图片地址返回。

回调的方式有两种,一种是callback,一种是Promise,个人比较喜欢Promise,所以下面以Promise为例子(callback方法其实一样的,可以看看官网例子):

1:我们修改最开始的IOSIntentModule.m文件:

#import "IOSIntentModule.h"
@implementation IOSIntentModule.h
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(test:(NSString *)name
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  resolve(name);
}
@end

在这里我们增加了两个参数。只要在方法参数的最后面(注意是最后面)加上“ resolver:(RCTPromiseResolveBlock)resolve

rejecter:(RCTPromiseRejectBlock)reject”这两个参数,就会产生一个Promise对象。

然后你就可以用resolve("信息")来返回正常信息。用reject("错误信息")来返回错误信息。

2:然后你就可以在RN中这样来接收这个返回值:

NativeModules.IOSIntentModule.test().then(msg => {
                                   //msg就是你用resolve返回的值
                                }).catch(error => {
                                    //error就是你用reject返回的值
                                });

但是这样的话,是用OC写的,我们如何用Swift来完成呢?

很简单,你在OC的test方法里,调用Swift的方法,然后接收Swift方法的返回值,再将其返回就可以了。那这里就涉及一个问题,如何在OC里调用Swift的方法呢?

OC调用Swift方法

这部分内容其实百度就可以知道了,具体步骤如下:

1:进入你项目的Build Settings里,将Defines Module设置为YES。

设置 Product Module Name ,也可以不设置,默认为工程的名字。

2:编写一个Swift类,就叫做IOSIntentModuleSwift.swift好了:

public class IOSIntentModuleSwift :NSObject{
    public func testSwift(_ name:String) -> String {
        return name
    }
 
}

注意这个类一定要继承NSObject,不然OC会找不到这个类。

还有就是参数最前面要加上“ _ ”,不然会报参数不匹配的错误(百度了一下说是swift3 的一个语法,参数必须有名字,但是从RN传过来的参数没有名字啥啥的,不懂iOS开发......总之加上“ _ ”就行了)。

3:在IOSIntentModule.m中添加头文件:“项目名-Swift.h” 。这个项目名就是之前让你设置的Product Module Name ,没设置的话就是你的工程名。

4:然后你就可以在OC中调用Swift了,就像这样子:

#import "IOSIntentModule.h"
#import "项目名-Swift.h"
@implementation IOSIntentModule.h
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(test:(NSString *)name
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    IOSIntentModuleSwift* intentSwift = [[IOSIntentModuleSwift alloc] init];
    NSString* str = [intentSwift testSwift:name];
    resolve(str);
}
@end

这样当调用到OC的test方法时,就会去执行Swift的testSwift方法,然后testSwift将其接收到的参数“name”返回,然后OC接收到Swift方法的返回值,再将其返回给RN。

正常来说这样就行了,但是现实总会有奇奇怪怪的事情发生。比如我公司的项目......加了“项目名-Swift.h”这个头文件后,总是编译不过去,说“项目名-Swift.h”里的一个@import xxxxx找不到。

解决办法也很简单。其实“项目名-Swift.h”就是一个桥接文件,里面的内容大概长这样:

SWIFT_CLASS("_TtC6alltuu20IOSIntentModuleSwift")
@interface IOSIntentModuleSwift : NSObject
- (NSDictionary * _Nonnull)pickImage SWIFT_WARN_UNUSED_RESULT;
- (NSString * _Nonnull)reactUplaodPicToOSS:(NSString * _Nonnull)bucket albumSetId:(NSString * _Nonnull)albumSetId objectkey:(NSString * _Nonnull)objectkey filePath:(NSString * _Nonnull)filePath contentId:(NSInteger)contentId type:(NSInteger)type SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

你可以自己新建一个比如说“IOSIntentModule-Swift.h”文件,然后把“项目名-Swift.h”文件的东西拿出来,去掉报错的地方,粘贴进去。然后在”IOSIntentModule.m“里直接“#import IOSIntentModule-Swift.h”就可以了。

iOS原生主动给RN发送事件

这一部分我卡了好久......后来我仔仔细细的看了下官网,发现有一句不起眼的话......

iOS原生与RN的通信(Swift版)

大家看最后一句,通过bridge向JS发送事件。

然后我尝试了一下,发现RCTRootView里就有一个bridge,这个bridge里有个eventDispatcher(),然后这个eventDispatcher()里就有三个发送事件的方法(虽然说是快过时了,但是可以用,而且用起来简单)

代码类似于这样子:

(ctrl.view as! RCTRootView).bridge.eventDispatcher().sendDeviceEvent(withName: "refresh", body: "Hello")

这边我们用的是sendDeviceEvent()方法,还有另外两种,不知道啥区别......大家可以自行百度。

其中withName:"refresh"表示你要发送的事件的名字,在RN中监听的时候,也要监听这个名字。body:"Hello"就是你要发送的数据。

然后你就可以在RN中监听这个事件:

    //组件渲染之前调用此方法
    componentWillMount() {
        this.subscription = DeviceEventEmitter.addListener('refresh', function(msg) {
            alert(msg);
        });
    }
    componentWillUnmount() {
        // 移除监听器
        this.subscription.remove();
    }

我们监听了refresh事件,当iOS原生那边发送的时候,就会被RN接收到。msg就是iOS原生那边的body参数。

结束

虽然没有全面将iOS原生与RN通信的方式全部写完。但是我觉得有这三个例子,看看官网,举一反三应该没啥问题。而且我们的重点是如何用Swift来实现通信的逻辑。

还有就是有问题多看看官网...逐字逐句的那种,官网超坑的(也可能是我太垃圾)。

那就这样结束了,才疏学浅,有不对的地方,还请大家批评指正。

最后

感谢我可爱的女朋友。

正文到此结束
Loading...