Native Module Development for React Native App (iOS)

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Native Module Development for React Native App (iOS)
Complex
~3-5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Developing Native Module for React Native App (iOS)

The bridge between JavaScript and native Swift/Objective-C is one of the trickiest layers in React Native. While the app works only with JS libraries, everything is relatively predictable. But once a task appears that can't be closed with standard package — working with Bluetooth Low Energy via CoreBluetooth, accessing protected Keychain via SecItemCopyMatching, integrating third-party bank or payment system SDK — you have to write Native Module manually.

And that's where it begins.

Bridge Architecture: Old and New

Before React Native 0.71, the bridge worked through an asynchronous message queue: JS thread serialized the call to JSON, sent through bridge, native thread deserialized and executed. Latency was acceptable for most tasks, but with high-frequency calls (for example, UI update from sensor data) it became noticeable.

Starting with version 0.68, New Architecture appeared — JSI (JavaScript Interface) + Turbo Modules. JSI allows calling native code synchronously through C++ host object, bypassing message queue. This fundamentally changes the approach to writing modules: instead of RCTBridgeModule you need to implement TurboModule protocol through code generation based on Flow/TypeScript specification.

In practice, most projects still sit on old architecture because updating breaks dependencies. So we support both approaches.

Old Architecture: RCTBridgeModule

Typical structure — Swift class inherited from NSObject with @objc attributes:

@objc(BiometricModule)
class BiometricModule: NSObject, RCTBridgeModule {
  static func moduleName() -> String { "BiometricModule" }

  @objc func authenticate(_ reason: String,
                           resolver: @escaping RCTPromiseResolveBlock,
                           rejecter: @escaping RCTPromiseRejectBlock) {
    let context = LAContext()
    var error: NSError?
    guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
      rejecter("BIOMETRIC_UNAVAILABLE", error?.localizedDescription, error)
      return
    }
    context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
                            localizedReason: reason) { success, authError in
      if success { resolver(true) }
      else { rejecter("AUTH_FAILED", authError?.localizedDescription, authError) }
    }
  }
}

Registration via RCT_EXTERN_MODULE in Objective-C bridging file is mandatory — without it the module doesn't appear in registry.

Most common error at this stage: developer writes Swift class, forgets to add @objc(BiometricModule) or incorrectly names method in RCT_EXTERN_METHOD, and on JS side gets undefined is not a function. Hard to debug because error appears at runtime without stacktrace.

Where Time Really Gets Spent

Thread safety. React Native calls module methods on arbitrary thread from its pool. If you access UIKit inside method — crash with UIKit called from background thread. Classic solution — DispatchQueue.main.async { } around UI code. But this creates new problem: resolve/reject are called asynchronously, and if user managed to close screen, completion handler accesses already freed object.

Pattern with [weak self] and guard is mandatory:

DispatchQueue.main.async { [weak self] in
  guard self != nil else { return }
  resolver(result)
}

Data serialization. Bridge accepts only types it can serialize to JSON: NSString, NSNumber, NSArray, NSDictionary, NSNull. Want to transfer Data (binary data) — encode to Base64. Want to transfer custom object — break it down to dictionary on native side. This is especially painful with CoreBluetooth when you need to return CBCharacteristic with all its properties.

Callbacks vs Promises vs Events. For one-time results — Promise. For stream of events (sensor data, connection status) — RCTEventEmitter. Mixing approaches in one module is mistake leading to memory leaks: if you save RCTResponseSenderBlock as property and call twice, app crashes with Tried to call a callback that is no longer valid.

New Architecture: Turbo Modules + Codegen

Starting with RN 0.70+, Codegen generates C++ abstraction from TypeScript specification. Spec file looks like:

import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  authenticate(reason: string): Promise<boolean>;
}

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

On native side implement NativeBiometricModuleSpec protocol that Codegen generated automatically. JSI allows calling methods synchronously without JSON serialization — speed is fundamentally different.

Problem: if project has at least one package without Turbo Module support, New Architecture will work in compatibility mode, partially losing benefits.

Approach to Implementation

Audit starts with analyzing current RN version, availability of JSI-compatible packages, and target iOS deployment. If project is on 0.72+ and team is ready for New Architecture — write Turbo Module with Codegen right away. If not — classic RCTBridgeModule with eye to future migration.

Unit test coverage of native part via XCTest is mandatory. Integration tests — via Detox or Jest with module mock on JS side.

Document public API in TypeScript types so team doesn't dig into native code every time.

What's Included

  • Analyzing requirements and choosing architectural approach (Old Bridge / Turbo Module)
  • Writing native code in Swift with Objective-C bridging
  • TypeScript typing of module public API
  • Error handling, thread safety
  • Unit tests of native part (XCTest)
  • Integration with JS layer, checking in simulator and on real device
  • Module usage documentation

Timeline

From 3 to 5 days depending on complexity of native API that needs to be wrapped. Simple wrapper over one system framework — closer to 3 days. Module with event stream, binary data, and New Architecture support — 5 days and more. Pricing is calculated individually after analyzing requirements and codebase.