React Native 与 iOS 原生通信:从理论到实践

引言

在现代移动应用开发中,React Native (RN)与原生平台的深度集成变得越来越重要。本文基于实际的 iOS 项目代码,详细介绍 RN 与 iOS 之间的通信机制,涵盖模块导出、事件发射、Promise 调用、桥连接等核心概念。

我们现在基于的是React Native的最新版本:v0.82.1。

一、基础架构搭建

1.1 React Native 视图集成

首先需要在 iOS 项目中正确集成 RN 视图。我们使用 RCTReactNativeFactory来创建和管理 RN 组件:

swift 复制代码
class ReactView: UIView {
  var reactNativeFactory: RCTReactNativeFactory?
  var reactNativeFactoryDelegate: RCTReactNativeFactoryDelegate?
  
  private func setupView() {
      reactNativeFactoryDelegate = ReactNativeDelegate()
      reactNativeFactoryDelegate!.dependencyProvider = RCTAppDependencyProvider()
      reactNativeFactory = RCTReactNativeFactory(delegate: reactNativeFactoryDelegate
      reactNativeFactory!.rootViewFactory.view(withModuleName: "lottieAnimation")
  }
}

1.2 调试与发布配置

通过自定义代理类处理不同环境的 bundle 加载:

swift 复制代码
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
    override func bundleURL() -> URL? {
      #if DEBUG
      RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
      #else
      Bundle.main.url(forResource: "main", withExtension: "jsbundle")
      #endif
    }
}

二、原生模块导出

2.1 基础模块定义

创建 NativeCommunicationModule类,作为 RN 调用原生功能的主要入口:

swift 复制代码
@objc(NativeCommunicationModule)
class NativeCommunicationModule: NSObject {
    
    @objc static func moduleName() -> String {
        return "NativeCommunicationModule"
    }
    
    @objc static func requiresMainQueueSetup() -> Bool {
        return true
    }
}

2.2 常量导出

向 RN 导出常量信息:

swift 复制代码
@objc func constantsToExport() -> [String: Any]! {
    return [
        "moduleVersion": "1.0.0",
        "platform": "iOS"
    ]
}

三、通信机制详解

3.1 Promise 异步通信

实现基于 Promise 的异步方法调用:

swift 复制代码
@objc
func getIntegrationInfo(_ resolve: @escaping RCTPromiseResolveBlock, 
                       reject: @escaping RCTPromiseRejectBlock) {
    let info: [String: Any] = [
        "platform": "iOS",
        "reactNativeVersion": "0.80+",
        "integrationMethod": "RCTReactNativeFactory + EventEmitter",
        "timestamp": Date().timeIntervalSince1970
    ]
    resolve(info)
}

RN 端调用方式:

typescript 复制代码
const { NativeCommunicationModule } = NativeModules;

const sendMessageToNativeWithPromise = async (message: string): Promise<any> => {
  return await NativeCommunicationModule.getIntegrationInfo(message);
};

3.2 同步方法调用

对于需要立即返回结果的方法,提供同步接口:

swift 复制代码
@objc
func getDeviceInfoSync() -> [String: Any] {
    return [
        "platform": "iOS",
        "systemVersion": UIDevice.current.systemVersion,
        "model": UIDevice.current.model,
        "timestamp": Date().timeIntervalSince1970
    ]
}

四、事件发射器(Event Emitter)

4.1 事件发射器实现

创建单例事件发射器,支持多种事件类型:

swift 复制代码
@objc(EventEmitterModule)
class EventEmitterModule: RCTEventEmitter {
    
    @objc static let shared = EventEmitterModule()
    
    override func supportedEvents() -> [String]! {
        return [
            "onNativeMessage",      // 原生消息事件
            "onDataUpdate",         // 数据更新事件
            "onStatusChange",       // 状态变化事件
            "onCustomEvent",        // 自定义事件
            "onTimerTick",          // 定时器事件
            "onNavigation",         // 导航事件
            "onError"               // 错误事件
        ]
    }
}

4.2 事件监听管理

swift 复制代码
private var hasListeners = false

override func startObserving() {
    hasListeners = true
    print("✅ JS 开始监听原生事件")
    startEventServices()
}

override func stopObserving() {
    hasListeners = false
    print("🛑 JS 停止监听原生事件")
    stopEventServices()
}

4.3 事件发送方法

swift 复制代码
@objc func sendMessage(_ message: String) {
    sendEvent(withName: "onNativeMessage", body: [
        "message": message,
        "timestamp": Date().timeIntervalSince1970,
        "type": "message"
    ])
}

五、核心桥接机制:连接 Swift 与 JavaScript(重要)

5.1 桥接文件的关键作用

桥接文件是整个通信架构的核心。React Native 的 iOS 端通信基于 Objective-C 运行时,JavaScript 只能通过 Objective-C 的桥接层来调用原生代码。没有这个桥连接文件会导致在RN侧找不到在Native侧定义的模块。

5.2 桥接文件怎么来

当你创建好.m文件或者.mm文件时候,先把所有文件移除引用,然后再添加到工程下面,Xcode会提示你是否创建"yourProject-Bridging-Header",然后选择是就可以了。

5.3桥接文件怎么来

创建好桥接文件,那么现在就可以在.mm或者.m文件中导出swift文件中定义的模块和方法了。

c++ 复制代码
// LottieBridge.mm - 核心桥接文件
#import "lottieAnimation-Bridging-Header.h"

// EventEmitterModule - 事件发射器桥接
@interface RCT_EXTERN_MODULE(EventEmitterModule, RCTEventEmitter)

// 事件发送方法
RCT_EXTERN_METHOD(sendMessage:(NSString *)message eventType:(NSString)eventType)
RCT_EXTERN_METHOD(sendDataUpdate:(NSDictionary *)data)
RCT_EXTERN_METHOD(sendStatusChange:(NSString *)status extraInfo:(NSDictionary *)extraInfo)
RCT_EXTERN_METHOD(sendCustomEvent:(NSString *)eventType data:(NSDictionary *)data)
RCT_EXTERN_METHOD(sendError:(NSString *)errorCode errorMessage:(NSString *)errorMessage details:(NSDictionary *)details)
RCT_EXTERN_METHOD(sendBatchEvents:(NSArray *)events)

// 同步属性访问
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(isBeingObserved)

@end

// NativeCommunicationModule - 功能模块桥接
@interface RCT_EXTERN_MODULE(NativeCommunicationModule, NSObject)

// Promise 方法
RCT_EXTERN_METHOD(getIntegrationInfo:(NSString *)message resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

// 普通方法
RCT_EXTERN_METHOD(sendToNative:(NSString *)message)

@end

做完以上的工作就可以在RN侧使用我们在原生定义的方法了。

六、双向通信实践

6.1 RN 到 iOS 的通信

RN 端发送消息到原生:

typescript 复制代码
const useSendMessageToNative = () => {
  const sendMessageToNative = (message: string): void => {
    NativeCommunicationModule.sendToNative(message);
  };
  
  return { sendMessageToNative };
};

iOS 端接收并处理消息:

swift 复制代码
@objc
func sendToNative(_ message: String) {
    DispatchQueue.main.async {
        print("📱 收到 RN 消息: \(message)")
        
        // 通知原生系统
        NotificationCenter.default.post(
            name: Notification.Name("ReactNativeMessage"),
            object: nil,
            userInfo: ["message": message]
        )
        
        // 通过 Event Emitter 发送回复
        EventEmitterModule.shared.sendMessage("已收到消息: \(message)")
    }
}

6.2 iOS 到 RN 的主动通信

原生端主动触发事件:

swift 复制代码
@objc
func triggerCustomEvent(_ eventType: String, data: [String: Any]?) {
    EventEmitterModule.shared.sendCustomEvent(eventType, data: data)
}

RN 端监听事件:

swift 复制代码
export const useNativeMessageToRN = () => {
  useEffect(() => {
    const handleNativeMessage = (messageFromNative) => {
      const {message, type} = messageFromNative;
      console.log('收到原生消息:', message);
      // 处理消息...
    };
    
    eventEmitter.addListener('onNativeMessage', handleNativeMessage);
    return () => eventEmitter.removeAllListeners('onNativeMessage');
  }, []);
};

七、内存管理与资源释放

7.1 关键的内存管理问题

在 RN 与 iOS 集成中,内存管理是至关重要的。如果不正确处理,会导致内存泄漏和应用崩溃。

7.2 视图控制器的内存管理

swift 复制代码
class ViewController: UIViewController {
    
    deinit {
        // 关键:在视图控制器销毁时进行清理
        ReactNativeViewManager.shared.cleanup()
    }
    
    @objc private func cleanupReactNative() {
        // 轻量级清理(推荐日常使用)
        ReactNativeViewManager.shared.cleanup()
    }
    
    @objc private func fullCleanupReactNative() {
        // 完整清理(释放所有资源)
        ReactNativeViewManager.shared.invalidateReactNative()
    }
}

7.3 React Native 视图管理器的内存管理

swift 复制代码
class ReactNativeViewManager {
    static let shared = ReactNativeViewManager()
    
    private var reactNativeFactory: RCTReactNativeFactory?
    private var currentRootView: RCTRootView?
    
    // 轻量级清理(保持 Factory 可用)
    func cleanup() {
        print("执行轻量级 RN 清理")
        currentRootView?.removeFromSuperview()
        currentRootView = nil
    }
    
    // 完整清理(释放所有资源)
    func invalidateReactNative() {
        print("执行完整 RN 资源释放")
        cleanup()
        reactNativeFactory = nil
    }
    
    deinit {
        // 确保资源被正确释放
        invalidateReactNative()
    }
}

7.4 事件发射器的内存管理

swift 复制代码
class EventEmitterModule: RCTEventEmitter {
    private var timer: Timer?
    
    override func stopObserving() {
        hasListeners = false
        // 停止定时器和其他服务
        stopTimer()
    }
    
    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
    
    deinit {
        // 最终清理
        stopTimer()
    }
}

7.5 RN 端的内存管理

在 React Native 组件中,也要正确管理事件监听器:

typescript 复制代码
export const useNativeMessageToRN = () => {
  useEffect(() => {
    const subscription = eventEmitter.addListener('onNativeMessage', handler);
    
    // 关键:在组件卸载时清理监听器
    return () => {
      subscription.remove();
    };
  }, []);
};

7.6 内存管理最佳实践

  1. 及时清理 :在 deinit和视图消失时释放资源
  2. 避免循环引用 :在闭包中使用 [weak self]
  3. 分层清理策略:提供轻量级和完整清理两种方式
  4. 事件监听器管理:确保监听器被正确移除
相关推荐
hashiqimiya2 小时前
html的input的required
java·前端·html
Mapmost2 小时前
WebGL三维模型标准(二)模型加载常见问题解决方案
前端
Mapmost2 小时前
Web端三维模型标准(一):单位与比例、多边形优化
前端
www_stdio3 小时前
JavaScript 执行机制详解:从 V8 引擎到执行上下文
前端·javascript
我命由我123453 小时前
HTML - 换行标签的 3 种写法(<br>、<br/>、<br />)
前端·javascript·css·html·css3·html5·js
暮冬十七3 小时前
[特殊字符] Vue3 项目最佳实践:组件命名、目录结构与类型规范指南
前端·前端架构·vue3项目搭建
F_Director3 小时前
简说Vue3 computed原理
前端·vue.js·面试
行走的陀螺仪3 小时前
Flutter 开发环境配置教程
android·前端·flutter·ios
焦糖小布丁3 小时前
代码签名证书如何有效消除Windows系统警告?
前端