---
name: mobile-react-native-native-modules
description: Accessing iOS-specific APIs not available in React Native
---



# React Native Native Modules

**Scope**: Native modules, bridging Swift/Objective-C, iOS APIs, TurboModules, native UI components
**Lines**: ~350
**Last Updated**: 2025-10-18

## When to Use This Skill

Activate this skill when:
- Accessing iOS-specific APIs not available in React Native
- Implementing performance-critical native functionality
- Integrating third-party iOS SDKs
- Creating custom native UI components
- Bridging existing Swift/Objective-C code to React Native
- Using iOS hardware features (Camera, Bluetooth, etc.)
- Implementing native background tasks or services

## Core Concepts

### Native Module Architecture

**Bridge Pattern**:
- JavaScript thread runs React Native code
- Native thread runs iOS code
- Bridge serializes data between threads (JSON-like)
- Asynchronous by default (callbacks/promises)

**TurboModules** (New Architecture):
- Direct JavaScript to native communication (no bridge)
- Type-safe with Codegen
- Synchronous methods possible
- Better performance (lazy loading)

**Communication Flow**:
```
JavaScript → Bridge → Native Module → iOS API
iOS API → Native Module → Bridge → JavaScript
```

### Swift Bridging

**Bridging Header** (Objective-C interop):
- Required for Swift modules in RN < 0.74
- Exposes Swift to Objective-C runtime
- Auto-generated by Xcode
- Manual updates for custom types

**@objc Attribute**:
- Makes Swift code visible to Objective-C
- Required for RCT macros
- Adds some runtime overhead
- Necessary for React Native bridge

### Event Emitters

**Push vs Pull**:
- Callbacks: One-time data return
- Promises: Single async result
- Events: Continuous stream of data

**Use Cases**:
- Sensor data (accelerometer, GPS)
- Notifications (local, push)
- Network state changes
- Background task updates

---

## Patterns

### Pattern 1: Basic Swift Native Module

```swift
// ios/MyModule.swift
import Foundation
import React

@objc(MyModule)
class MyModule: NSObject {

  // MARK: - React Native Export

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false // Run on background thread
  }

  @objc
  func greet(_ name: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
    let greeting = "Hello, \(name)!"
    resolver(greeting)
  }

  @objc
  func calculateSum(_ a: NSNumber, b: NSNumber, callback: @escaping RCTResponseSenderBlock) {
    let sum = a.doubleValue + b.doubleValue
    callback([NSNull(), sum]) // [error, result] pattern
  }

  @objc
  func getDeviceInfo() -> NSDictionary {
    return [
      "model": UIDevice.current.model,
      "systemVersion": UIDevice.current.systemVersion,
      "name": UIDevice.current.name
    ]
  }
}
```

```objective-c
// ios/MyModule.m (Bridging file)
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyModule, NSObject)

RCT_EXTERN_METHOD(greet:(NSString *)name
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(calculateSum:(nonnull NSNumber *)a
                  b:(nonnull NSNumber *)b
                  callback:(RCTResponseSenderBlock)callback)

RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getDeviceInfo)

@end
```

```typescript
// src/modules/MyModule.ts
import { NativeModules } from 'react-native';

interface MyModuleInterface {
  greet(name: string): Promise<string>;
  calculateSum(a: number, b: number, callback: (error: any, result: number) => void): void;
  getDeviceInfo(): { model: string; systemVersion: string; name: string };
}

const { MyModule } = NativeModules;

export default MyModule as MyModuleInterface;
```

**Benefits**:
- Type-safe TypeScript interface
- Async methods with promises
- Synchronous methods for simple data
- Callback pattern for legacy code

### Pattern 2: Event Emitter Native Module

```swift
// ios/SensorModule.swift
import Foundation
import CoreMotion
import React

@objc(SensorModule)
class SensorModule: RCTEventEmitter {

  private var motionManager: CMMotionManager?
  private var isObserving = false

  override init() {
    super.init()
    motionManager = CMMotionManager()
  }

  // MARK: - React Native Export

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

  override func supportedEvents() -> [String]! {
    return ["onAccelerometerData", "onDeviceMotion"]
  }

  @objc
  func startAccelerometer() {
    guard let manager = motionManager, !isObserving else { return }

    isObserving = true
    manager.accelerometerUpdateInterval = 0.1

    manager.startAccelerometerUpdates(to: .main) { [weak self] (data, error) in
      guard let data = data, error == nil else { return }

      self?.sendEvent(withName: "onAccelerometerData", body: [
        "x": data.acceleration.x,
        "y": data.acceleration.y,
        "z": data.acceleration.z,
        "timestamp": Date().timeIntervalSince1970
      ])
    }
  }

  @objc
  func stopAccelerometer() {
    motionManager?.stopAccelerometerUpdates()
    isObserving = false
  }

  override func stopObserving() {
    stopAccelerometer()
  }
}
```

```objective-c
// ios/SensorModule.m
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(SensorModule, RCTEventEmitter)

RCT_EXTERN_METHOD(startAccelerometer)
RCT_EXTERN_METHOD(stopAccelerometer)

@end
```

```typescript
// src/modules/SensorModule.ts
import { NativeEventEmitter, NativeModules } from 'react-native';

const { SensorModule } = NativeModules;
const sensorEmitter = new NativeEventEmitter(SensorModule);

export interface AccelerometerData {
  x: number;
  y: number;
  z: number;
  timestamp: number;
}

export const startAccelerometer = () => {
  SensorModule.startAccelerometer();
};

export const stopAccelerometer = () => {
  SensorModule.stopAccelerometer();
};

export const subscribeToAccelerometer = (
  callback: (data: AccelerometerData) => void
) => {
  const subscription = sensorEmitter.addListener('onAccelerometerData', callback);
  return () => subscription.remove();
};
```

**When to use**:
- Continuous data streams (sensors, location)
- Push notifications or system events
- Real-time updates from native code
- Background task status updates

### Pattern 3: Native UI Component (Swift)

```swift
// ios/CircularProgressView.swift
import UIKit

class CircularProgressView: UIView {

  private var progressLayer = CAShapeLayer()
  private var trackLayer = CAShapeLayer()

  @objc var progress: CGFloat = 0 {
    didSet {
      updateProgress()
    }
  }

  @objc var progressColor: UIColor = .systemBlue {
    didSet {
      progressLayer.strokeColor = progressColor.cgColor
    }
  }

  @objc var trackColor: UIColor = .systemGray5 {
    didSet {
      trackLayer.strokeColor = trackColor.cgColor
    }
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    setupLayers()
  }

  private func setupLayers() {
    let center = CGPoint(x: bounds.midX, y: bounds.midY)
    let radius = min(bounds.width, bounds.height) / 2 - 10
    let circularPath = UIBezierPath(
      arcCenter: center,
      radius: radius,
      startAngle: -.pi / 2,
      endAngle: 3 * .pi / 2,
      clockwise: true
    )

    // Track layer
    trackLayer.path = circularPath.cgPath
    trackLayer.fillColor = UIColor.clear.cgColor
    trackLayer.strokeColor = trackColor.cgColor
    trackLayer.lineWidth = 10
    trackLayer.lineCap = .round

    // Progress layer
    progressLayer.path = circularPath.cgPath
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.strokeColor = progressColor.cgColor
    progressLayer.lineWidth = 10
    progressLayer.lineCap = .round
    progressLayer.strokeEnd = 0

    layer.addSublayer(trackLayer)
    layer.addSublayer(progressLayer)
  }

  private func updateProgress() {
    CATransaction.begin()
    CATransaction.setAnimationDuration(0.3)
    progressLayer.strokeEnd = progress
    CATransaction.commit()
  }
}
```

```swift
// ios/CircularProgressViewManager.swift
import React

@objc(CircularProgressViewManager)
class CircularProgressViewManager: RCTViewManager {

  override static func requiresMainQueueSetup() -> Bool {
    return true // UI components need main thread
  }

  override func view() -> UIView! {
    return CircularProgressView()
  }
}
```

```objective-c
// ios/CircularProgressViewManager.m
#import <React/RCTViewManager.h>

@interface RCT_EXTERN_MODULE(CircularProgressViewManager, RCTViewManager)

RCT_EXPORT_VIEW_PROPERTY(progress, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(progressColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(trackColor, UIColor)

@end
```

```typescript
// src/components/CircularProgress.tsx
import React from 'react';
import { requireNativeComponent, ViewProps } from 'react-native';

interface CircularProgressProps extends ViewProps {
  progress: number; // 0.0 - 1.0
  progressColor?: string;
  trackColor?: string;
}

const NativeCircularProgress = requireNativeComponent<CircularProgressProps>(
  'CircularProgressView'
);

export const CircularProgress: React.FC<CircularProgressProps> = (props) => {
  return <NativeCircularProgress {...props} />;
};
```

**Benefits**:
- Native iOS animations and rendering
- Full access to UIKit components
- Performance for complex UI
- Reusable across React Native apps

### Pattern 4: Async Native Operations with Error Handling

```swift
// ios/NetworkModule.swift
import Foundation
import React

@objc(NetworkModule)
class NetworkModule: NSObject {

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }

  @objc
  func fetchUser(_ userId: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {

    guard let url = URL(string: "https://api.example.com/users/\(userId)") else {
      rejecter("INVALID_URL", "Invalid user ID", nil)
      return
    }

    URLSession.shared.dataTask(with: url) { data, response, error in
      // Handle network error
      if let error = error {
        rejecter("NETWORK_ERROR", error.localizedDescription, error)
        return
      }

      // Handle HTTP error
      guard let httpResponse = response as? HTTPURLResponse else {
        rejecter("INVALID_RESPONSE", "Invalid response type", nil)
        return
      }

      guard (200...299).contains(httpResponse.statusCode) else {
        rejecter("HTTP_ERROR", "Status code: \(httpResponse.statusCode)", nil)
        return
      }

      // Parse JSON
      guard let data = data else {
        rejecter("NO_DATA", "No data received", nil)
        return
      }

      do {
        let json = try JSONSerialization.jsonObject(with: data, options: [])
        resolver(json)
      } catch {
        rejecter("PARSE_ERROR", "Failed to parse JSON", error)
      }
    }.resume()
  }
}
```

```typescript
// src/modules/NetworkModule.ts
import { NativeModules } from 'react-native';

interface User {
  id: string;
  name: string;
  email: string;
}

class NetworkModuleWrapper {
  async fetchUser(userId: string): Promise<User> {
    try {
      const user = await NativeModules.NetworkModule.fetchUser(userId);
      return user as User;
    } catch (error: any) {
      // Error codes from native module
      switch (error.code) {
        case 'INVALID_URL':
          throw new Error('Invalid user ID format');
        case 'NETWORK_ERROR':
          throw new Error('Network connection failed');
        case 'HTTP_ERROR':
          throw new Error(`Server error: ${error.message}`);
        case 'PARSE_ERROR':
          throw new Error('Invalid response format');
        default:
          throw error;
      }
    }
  }
}

export default new NetworkModuleWrapper();
```

**When to use**:
- Network requests with iOS URLSession
- File system operations
- Database queries
- Any async native operation

### Pattern 5: Swift Concurrency Integration

```swift
// ios/ModernAsyncModule.swift
import Foundation
import React

@objc(ModernAsyncModule)
class ModernAsyncModule: NSObject {

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }

  @objc
  func performAsyncWork(_ input: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {

    Task {
      do {
        let result = try await processData(input)
        resolver(result)
      } catch {
        rejecter("ASYNC_ERROR", error.localizedDescription, error)
      }
    }
  }

  private func processData(_ input: String) async throws -> [String: Any] {
    // Simulate async work
    try await Task.sleep(nanoseconds: 1_000_000_000)

    // Use structured concurrency
    async let part1 = fetchPart1(input)
    async let part2 = fetchPart2(input)

    let results = try await [part1, part2]

    return [
      "input": input,
      "results": results,
      "timestamp": Date().timeIntervalSince1970
    ]
  }

  private func fetchPart1(_ input: String) async throws -> String {
    try await Task.sleep(nanoseconds: 500_000_000)
    return "Part1: \(input)"
  }

  private func fetchPart2(_ input: String) async throws -> String {
    try await Task.sleep(nanoseconds: 500_000_000)
    return "Part2: \(input)"
  }
}
```

**Benefits**:
- Modern Swift async/await syntax
- Structured concurrency patterns
- Better error handling
- Cancellation support

### Pattern 6: Objective-C Native Module (Legacy)

```objective-c
// ios/LegacyModule.h
#import <React/RCTBridgeModule.h>

@interface LegacyModule : NSObject <RCTBridgeModule>
@end
```

```objective-c
// ios/LegacyModule.m
#import "LegacyModule.h"

@implementation LegacyModule

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addNumbers:(NSInteger)a
                  b:(NSInteger)b
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  NSInteger sum = a + b;
  resolve(@(sum));
}

RCT_EXPORT_METHOD(performCallback:(RCTResponseSenderBlock)callback)
{
  callback(@[[NSNull null], @"Success"]);
}

+ (BOOL)requiresMainQueueSetup
{
  return NO;
}

@end
```

**When to use**:
- Legacy codebases
- Existing Objective-C libraries
- Team familiar with Objective-C
- Interoperating with C/C++ code

---

## Quick Reference

### Bridge Data Types

```
JavaScript Type    | Objective-C/Swift Type       | Notes
-------------------|------------------------------|------------------
number             | NSNumber / Double / Int      | Auto-converted
string             | NSString / String            | UTF-8 encoded
boolean            | NSNumber / Bool              | true/false
null/undefined     | NSNull / nil                 | Check for NSNull
Array              | NSArray / [Any]              | Ordered collection
Object             | NSDictionary / [String: Any] | Key-value pairs
Function           | RCTResponseSenderBlock       | Callback
Promise            | RCTPromise(Resolve/Reject)   | Async result
```

### Module Export Macros

```objective-c
RCT_EXPORT_MODULE()                      // Export module with class name
RCT_EXPORT_MODULE(CustomName)            // Export with custom name
RCT_EXPORT_METHOD(methodName:...)        // Export async method
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD() // Export synchronous method
RCT_EXPORT_VIEW_PROPERTY(name, type)     // Export view property
```

### Best Practices

```
✅ DO: Use promises for async operations
✅ DO: Validate input parameters
✅ DO: Return descriptive error codes
✅ DO: Run on background thread when possible
✅ DO: Use type-safe TypeScript interfaces

❌ DON'T: Block the main thread with heavy operations
❌ DON'T: Return functions or callbacks from native
❌ DON'T: Store references to JavaScript objects
❌ DON'T: Use synchronous methods for slow operations
❌ DON'T: Forget to handle errors properly
```

---

## Anti-Patterns

❌ **Synchronous heavy operations**: Blocking main thread with slow native code
✅ Use promises and background threads: `requiresMainQueueSetup() -> false`

❌ **Over-bridging**: Calling native for simple logic better done in JS
✅ Use native only for platform APIs, performance-critical code, or third-party SDKs

❌ **No error handling**: Native errors crash the app
✅ Always use try-catch and reject promises with descriptive error codes

❌ **Storing JavaScript callbacks**: Memory leaks and crashes
✅ Use event emitters for continuous updates, promises for one-time results

❌ **Large data serialization**: Sending MB of data across bridge
✅ Use file paths or shared memory, send only metadata

❌ **Main thread for async work**: UI freezes during operations
✅ Use background threads for network, file I/O, computations

❌ **Missing TypeScript types**: Untyped native module usage
✅ Create interfaces for all native modules with full type safety

❌ **Not cleaning up resources**: Memory leaks from sensors, timers, etc.
✅ Implement `stopObserving` and cleanup in `dealloc`/`deinit`

---

## Related Skills

- `react-native-setup.md` - Project configuration for native modules
- `react-native-performance.md` - Optimizing bridge communication
- `swift-concurrency.md` - Modern Swift async patterns
- `ios-networking.md` - URLSession and network best practices
- `swiftui-architecture.md` - iOS app structure and patterns
- `ios-testing.md` - Testing native modules

---

**Last Updated**: 2025-10-18
**Format Version**: 1.0 (Atomic)
