DEV Community

Amit Kumar
Amit Kumar

Posted on

Bridging Swift with React Native's New Architecture using TurboModules

With the release of React Native 0.79 and React 19, bridging native modules to JavaScript has become even more seamless using the TurboModules system. In this guide, we’ll walk through how to create a Swift-based TurboModule and expose native functionality to your React Native app.


Project Setup

We assume you're using the latest versions:

{
  "dependencies": {
    "react": "19.0.0",
    "react-native": "0.79.3"
  }
}


Enter fullscreen mode Exit fullscreen mode

Directory Structure

Your project should resemble:

yourapp/
├── ios/
   ├── NativeMessageModule/
      ├── NativeMessageModule.swift
      └── NativeMessageModule-Swift.m
├── specs/
   └── NativeMessageModule.ts
├── App.js
├── package.json
└── ...


Enter fullscreen mode Exit fullscreen mode

Define the TurboModule Interface

Create specs/NativeMessageModule.ts:

import {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
  sendMessage(message: string): void;
  getMessage(): Promise<string>;
  sendWithCallback(callback: (response: string) => void): void;

  addListener(eventName: string): void;
  removeListeners(count: number): void;
  startSendingEvents(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeMessageModule');


Enter fullscreen mode Exit fullscreen mode

✅ All native TurboModules must begin with the Native prefix and match the registered module name exactly (NativeMessageModule here).


Configure Codegen

In your package.json, add:

"codegenConfig": {
  "name": "NativeMessageModule",
  "type": "modules",
  "jsSrcsDir": "specs",
  "android": {
    "javaPackageName": "com.turbomodules" #Replace this with your packagename
  }
}


Enter fullscreen mode Exit fullscreen mode

Then, inside the ios folder:

cd ios
bundle install
bundle exec pod install


Enter fullscreen mode Exit fullscreen mode

Implement Native Code in Swift

1. Native Bridge Header (Objective-C)

Create ios/NativeMessageModule/NativeMessageModule-Swift.m:

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(NativeMessageModule, RCTEventEmitter)

RCT_EXTERN_METHOD(sendMessage:(NSString *)message)

RCT_EXTERN_METHOD(getMessage:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(sendWithCallback:(RCTResponseSenderBlock)callback)

RCT_EXTERN_METHOD(startSendingEvents)

@end


Enter fullscreen mode Exit fullscreen mode

2. Swift Implementation

Create ios/NativeMessageModule/NativeMessageModule.swift:


import Foundation
import React

@objc(NativeMessageModule)
class NativeMessageModule: RCTEventEmitter {

    private var hasListeners = false

    override static func requiresMainQueueSetup() -> Bool {
        return true
    }

    override func supportedEvents() -> [String]! {
        return ["onTimerTick"]
    }

    override func startObserving() {
        hasListeners = true
    }

    override func stopObserving() {
        hasListeners = false
    }

    @objc
    func sendMessage(_ message: String) {
        NSLog("[iOS TurboModule] Received from JS: %@", message)
    }

    @objc
    func getMessage(_ resolve: @escaping RCTPromiseResolveBlock,
                    reject: @escaping RCTPromiseRejectBlock) {
        let response = "Hello from iOS TurboModule"
        resolve(response)
    }

    @objc
    func sendWithCallback(_ callback: @escaping RCTResponseSenderBlock) {
        callback(["Callback response from iOS TurboModule"])
    }

    @objc
    func startSendingEvents() {
        guard hasListeners else { return }

        for i in 1...5 {
            DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) { [weak self] in
                guard let self = self, self.hasListeners else { return }
                let eventData = ["message": "iOS TurboModule Event at \(Date().timeIntervalSince1970)"]
                self.sendEvent(withName: "onTimerTick", body: eventData)
            }
        }
    }
}


Enter fullscreen mode Exit fullscreen mode


Reinstall Pods After Codegen

After creating Swift files and configuring the bridging header:

cd ios
bundle install
bundle exec pod install


Enter fullscreen mode Exit fullscreen mode

React Native Integration

Use the TurboModule in your App.js:


import React, {useState, useEffect} from 'react';
import {
  View,
  Button,
  Text,
  StyleSheet,
  NativeEventEmitter,
} from 'react-native';
import NativeMessageModule from './specs/NativeMessageModule';

const App = () => {
  const [receivedMessage, setReceivedMessage] = useState('');
  const [lastSentMessage, setLastSentMessage] = useState('');
  const [callbackMsg, setCallbackMsg] = useState('');
  const [eventMsg, setEventMsg] = useState('');

  useEffect(() => {
    const eventEmitter = new NativeEventEmitter(NativeMessageModule);
    const subscription = eventEmitter.addListener('onTimerTick', event => {
      setEventMsg(event.message);
    });
    return () => subscription.remove();
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Bridging Example</Text>

      <Button title="Send Message to Native" onPress={() => {
        const msg = 'Hello from React Native';
        NativeMessageModule.sendMessage(msg);
        setLastSentMessage(msg);
      }} />
      <Text style={styles.message}>Last sent: {lastSentMessage}</Text>

      <Button title="Get Message from Native" onPress={async () => {
        const msg = await NativeMessageModule.getMessage();
        setReceivedMessage(msg);
      }} />
      <Text style={styles.message}>Received: {receivedMessage}</Text>

      <Button title="Use Callback" onPress={() => {
        NativeMessageModule.sendWithCallback(msg => setCallbackMsg(msg));
      }} />
      <Text>Received: {callbackMsg}</Text>

      <Button title="Trigger Native Event" onPress={() => {
        NativeMessageModule.startSendingEvents();
      }} />
      <Text>Received: {eventMsg}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {flex: 1, justifyContent: 'center', padding: 16},
  title: {fontSize: 24, fontWeight: 'bold', marginBottom: 32, textAlign: 'center'},
  message: {marginTop: 8, fontSize: 14, color: '#666'},
});

export default App;


Enter fullscreen mode Exit fullscreen mode

📌 Summary

In this blog, we:

✅ Defined a TurboModule spec in TypeScript
✅ Created Swift native module logic compatible with the New Architecture
✅ Configured the Objective-C Bridging Header
✅ Handled Promises, Callbacks, and native event emissions
✅ Used the module in React Native with hooks and state

Top comments (0)