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 <React/RCTBridgeModule.h>
#import <React/RCTLog.h>

@interface IOSIntentModule : NSObject <RCTBridgeModule>
@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 <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(IOSIntentModule, NSObject)

RCT_EXTERN_METHOD(test:(NSString *)name)

@end

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

#import <React/RCTBridgeModule.h>

然后你就可以在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发送事件

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


官网介绍.png

大家看最后一句,通过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来实现通信的逻辑。

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

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

最后

感谢我可爱的女朋友。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容

  • 弯弯的月亮 挂在深秋的夜空 毫不吝啬的洒满清辉 在苍满的大地上 苍茫的大地一片凄凉 寂静取代了喧嚣 城市在灯火阑珊...
    阿长的沙鸥阅读 513评论 0 1
  • 早起于我来说,也不是很困难的一件事。身体里的生物钟总是能在清晨的五六点钟将我唤醒。就这样,我可以有很多安静与...
    冯蓉阅读 200评论 0 3