Nitro Modules로 네이티브 모듈 만들어보기

JavaScript 객체를 C++로 구현하는 새로운 방법


Nitro Modules란?

JavaScript 객체를 C++/Swift/Kotlin로 구현할 수 있는 프레임워크다. JSI 기반으로 동작하며, TurboModules보다 16배 빠르다.

성능

100,000번 호출 기준:

작업ExpoModulesTurboModulesNitro
addNumbers()434.85ms115.86ms7.27ms
addStrings()429.53ms179.02ms29.94ms

TurboModules보다 16배, ExpoModules보다 60배 빠르다.

왜 빠른가?

Lightweight Layer

  • 타입 체크는 컴파일 타임에만 수행
  • 런타임 오버헤드 제로

Direct Swift ↔ C++ Interop

  • TurboModules/ExpoModules와 달리 Objective-C를 전혀 사용하지 않음
  • Swift와 C++을 직접 연결 (close to zero-overhead)

jsi::NativeState 사용

  • TurboModules의 jsi::HostObject보다 효율적
  • Proper native prototypes, GC 최적화

완벽한 타입 & Null 안전성

  • TypeScript spec = 단일 진실 공급원
  • 컴파일 타임 타입 체크로 런타임 에러 차단
  • Null safety 보장

객체 지향 & 현대적 언어

  • HybridObject: 생성, 전달, 소멸 가능한 네이티브 객체
  • Swift async/await, Kotlin Coroutines 직접 지원
  • Protocols (Swift), Interfaces (Kotlin)

동작 원리

JSI (JavaScript Interface)

기존 Bridge는 비동기 메시지로 통신한다:

JavaScript Thread → JSON 직렬화 → Bridge Queue → Native Thread
(메모리 복사, 느림, 비동기만 가능)

JSI는 직접 호출한다:

JavaScript → C++ 함수 직접 호출 → 즉시 반환
(Zero-copy, 빠름, 동기/비동기 모두 가능)

개발자 vs Nitrogen 역할 분담

개발자가 작성:

  1. TypeScript Spec (.nitro.ts) - 인터페이스
  2. C++ 구현 (.hpp, .cpp) - 실제 로직
  3. nitro.json - Autolinking 설정

Nitrogen이 자동 생성:

  1. Abstract Class (HybridStringUtilsSpec.hpp)
  2. iOS/Android 브릿지 (Swift/Kotlin ↔ C++)
  3. 등록 코드 (HybridObjectRegistry::register...)

등록 & 호출 과정

앱 시작 시 Nitro가 자동 생성한 등록 코드가 실행된다:

HybridObjectRegistry::registerHybridObjectConstructor(
  "StringUtils",  // JS에서 사용할 이름
  []() { return std::make_shared<HybridStringUtils>(); }
);

JavaScript에서는 이렇게 호출한다:

const module = NitroModules.createHybridObject<StringUtils>("StringUtils");
const result = module.reverse("hello"); // C++ 직접 실행!

실행 흐름:

1. module.reverse('hello') 호출
2. JSI가 C++ HybridStringUtils::reverse() 직접 실행
3. std::reverse() 로직 수행
4. 결과 즉시 반환 ("olleh")

메모리 & 성능

Bridge 방식:

JS Object → JSON 직렬화 → 메모리 복사 → Native Object
(느림, 메모리 2배)

Nitro 방식:

JS → JSI → C++ (메모리 공유)
(빠름, Zero-copy)

구현

1. TypeScript Spec 정의

import type { HybridObject } from "react-native-nitro-modules";
 
/**
 * 문자열 조작을 위한 유틸리티 HybridObject
 */
export interface StringUtils
  extends HybridObject<{
    ios: "c++";
    android: "c++";
  }> {
  /**
   * 문자열을 역순으로 뒤집기
   */
  reverse(text: string): string;
 
  /**
   * 대문자로 변환
   */
  toUpper(text: string): string;
 
  /**
   * 소문자로 변환
   */
  toLower(text: string): string;
 
  /**
   * 첫 글자만 대문자로
   */
  capitalize(text: string): string;
 
  /**
   * 문자열 반복
   */
  repeatText(text: string, count: number): string;
 
  /**
   * 회문(palindrome) 체크
   */
  isPalindrome(text: string): boolean;
 
  /**
   * 두 문자열 길이 합산
   */
  getTotalLength(str1: string, str2: string): number;
}

포인트:

  • HybridObject extend
  • ios: 'c++', android: 'c++' 지정
  • Swift 예약어 주의 (repeat → repeatText)

2. C++ 구현

#pragma once
 
#include "HybridStringUtilsSpec.hpp"
#include <string>
#include <algorithm>
#include <cctype>
 
namespace margelo::nitro::hellonitro {
 
using namespace margelo::nitro;
 
class HybridStringUtils : public HybridStringUtilsSpec {
public:
  HybridStringUtils() : HybridObject(TAG) {}
 
  std::string reverse(const std::string& text) override;
  std::string toUpper(const std::string& text) override;
  std::string toLower(const std::string& text) override;
  std::string capitalize(const std::string& text) override;
  std::string repeatText(const std::string& text, double count) override;
  bool isPalindrome(const std::string& text) override;
  double getTotalLength(const std::string& str1, const std::string& str2) override;
 
  void loadHybridMethods() override {
    registerHybrids(this, [](Prototype& prototype) {
      prototype.registerHybridMethod("reverse", &HybridStringUtils::reverse);
      prototype.registerHybridMethod("toUpper", &HybridStringUtils::toUpper);
      prototype.registerHybridMethod("toLower", &HybridStringUtils::toLower);
      prototype.registerHybridMethod("capitalize", &HybridStringUtils::capitalize);
      prototype.registerHybridMethod("repeatText", &HybridStringUtils::repeatText);
      prototype.registerHybridMethod("isPalindrome", &HybridStringUtils::isPalindrome);
      prototype.registerHybridMethod("getTotalLength", &HybridStringUtils::getTotalLength);
    });
  }
 
private:
  static constexpr auto TAG = "StringUtils";
};
 
} // namespace margelo::nitro::hellonitro

포인트:

  • ⚠️ 네임스페이스: margelo::nitro::hellonitro (필수!)
  • Spec을 상속하고 메서드 구현
  • Default constructor 필수

3. nitro.json 설정

{
  "$schema": "https://nitro.margelo.com/nitro.schema.json",
  "cxxNamespace": ["hellonitro"],
  "ios": {
    "iosModuleName": "NitroHelloNitro"
  },
  "android": {
    "androidNamespace": ["hellonitro"],
    "androidCxxLibName": "NitroHelloNitro"
  },
  "autolinking": {
    "StringUtils": {
      "cpp": "HybridStringUtils"
    }
  },
  "ignorePaths": ["**/node_modules"]
}

Autolinking 필수 조건:

  • 파일명 = 클래스명 (HybridStringUtils.hpp)
  • Default constructor 필수
  • 네임스페이스 일치

Nitrogen 코드 생성

npm run specs  # TypeScript spec → 네이티브 브릿지 자동 생성

생성되는 파일:

nitrogen/generated/
├── shared/c++/
│   └── HybridStringUtilsSpec.hpp  ← Abstract class
├── ios/
│   └── NitroHelloNitroAutolinking.mm  ← 자동 등록 코드
└── android/
    └── NitroHelloNitroOnLoad.cpp     ← 자동 등록 코드

빌드 & 실행

React Native CLI

# iOS
cd ios && pod install && cd ..
npx react-native run-ios
 
# Android
npx react-native run-android

Expo (Development Build)

npx expo prebuild
npx expo run:ios

사용

import { NitroModules } from "react-native-nitro-modules";
import type { StringUtils } from "hello-nitro";
 
const StringUtilsModule =
  NitroModules.createHybridObject<StringUtils>("StringUtils");
 
// JSI를 통해 C++ 직접 호출!
const result = StringUtilsModule.reverse("hello"); // "olleh"

성능 비교

10,000번 반복 벤치마크:

// Pure JavaScript
console.time("JS");
for (let i = 0; i < 10000; i++) {
  "hello".split("").reverse().join("");
}
console.timeEnd("JS");
// JS: ~15ms
 
// Nitro Modules
console.time("Nitro");
for (let i = 0; i < 10000; i++) {
  StringUtilsModule.reverse("hello");
}
console.timeEnd("Nitro");
// Nitro: ~3ms ⚡
구분BridgeNitro
통신 방식JSON 직렬화직접 호출
메모리복사공유 (Zero-copy)
호출 방식비동기만동기/비동기
성능느림5배 빠름

용어 정리

개념설명
HybridObjectJS ↔ 네이티브 연결 객체
NitrogenTypeScript → 네이티브 브릿지 자동 생성
JSIJS에서 C++ 직접 호출 (브릿지 없음)
Autolinking자동 등록 (Default constructor 필수)
Namespacemargelo::nitro::hellonitro 필수

전체 아키텍처

┌─────────────────────────────────┐
│    1. TypeScript Spec 작성      │
│  export interface StringUtils   │
└────────────┬────────────────────┘
             │
             ▼
┌─────────────────────────────────┐
│    2. Nitrogen 코드 생성        │
│       (npm run specs)           │
│  - Abstract Class               │
│  - iOS/Android Bridges          │
│  - Autolinking Registration     │
└────────────┬────────────────────┘
             │
             ▼
┌─────────────────────────────────┐
│    3. C++ 구현                  │
│  HybridStringUtils::reverse()  │
└────────────┬────────────────────┘
             │
             ▼
┌─────────────────────────────────┐
│    4. 앱 시작 시 등록           │
│  HybridObjectRegistry::         │
│  registerHybridObject(...)      │
└────────────┬────────────────────┘
             │
             ▼
┌─────────────────────────────────┐
│    5. JavaScript 호출           │
│  module.reverse('hello')        │
│         ↓ JSI (직접)            │
│  C++ reverse() 실행             │
│         ↓                       │
│  결과 즉시 반환                 │
└─────────────────────────────────┘

비교

TurboModulesNitro
성능JSI 기반16배 빠름
언어Objective-C 필요Swift/Kotlin 직접
타입 안전성Flow 기반완벽 (TS 단일 공급원)
객체HostObjectNativeState (더 효율적)
런타임 오버헤드있음제로

정리

Nitro는 JavaScript 객체를 C++/Swift/Kotlin로 구현할 수 있게 해준다. JSI 기반으로 동작하며, Nitrogen이 TypeScript interface에서 네이티브 바인딩을 자동 생성한다.

TurboModules보다 16배, ExpoModules보다 60배 빠르다. Swift와 C++을 직접 연결하며 Objective-C를 사용하지 않는다. jsi::HostObject 대신 jsi::NativeState를 사용해 더 효율적이다.

타입 체크는 컴파일 타임에만 수행되어 런타임 오버헤드가 없다. TypeScript에서 number로 선언하면 네이티브에서 반드시 Double로 구현해야 하며, 그렇지 않으면 컴파일 에러가 발생한다.

Swift의 async/await, Kotlin의 Coroutines를 직접 지원한다.

참고

Nitro Modules로 네이티브 모듈 만들어보기