Nitro Modules로 네이티브 모듈 만들어보기
JavaScript 객체를 C++로 구현하는 새로운 방법
Nitro Modules란?
JavaScript 객체를 C++/Swift/Kotlin로 구현할 수 있는 프레임워크다. JSI 기반으로 동작하며, TurboModules보다 16배 빠르다.
성능
100,000번 호출 기준:
| 작업 | ExpoModules | TurboModules | Nitro |
|---|---|---|---|
addNumbers() | 434.85ms | 115.86ms | 7.27ms |
addStrings() | 429.53ms | 179.02ms | 29.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, KotlinCoroutines직접 지원 - Protocols (Swift), Interfaces (Kotlin)
동작 원리
JSI (JavaScript Interface)
기존 Bridge는 비동기 메시지로 통신한다:
JavaScript Thread → JSON 직렬화 → Bridge Queue → Native Thread
(메모리 복사, 느림, 비동기만 가능)
JSI는 직접 호출한다:
JavaScript → C++ 함수 직접 호출 → 즉시 반환
(Zero-copy, 빠름, 동기/비동기 모두 가능)
개발자 vs Nitrogen 역할 분담
개발자가 작성:
- TypeScript Spec (
.nitro.ts) - 인터페이스 - C++ 구현 (
.hpp,.cpp) - 실제 로직 nitro.json- Autolinking 설정
Nitrogen이 자동 생성:
- Abstract Class (
HybridStringUtilsSpec.hpp) - iOS/Android 브릿지 (Swift/Kotlin ↔ C++)
- 등록 코드 (
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;
}포인트:
HybridObjectextendios: '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-androidExpo (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 ⚡| 구분 | Bridge | Nitro |
|---|---|---|
| 통신 방식 | JSON 직렬화 | 직접 호출 |
| 메모리 | 복사 | 공유 (Zero-copy) |
| 호출 방식 | 비동기만 | 동기/비동기 |
| 성능 | 느림 | 5배 빠름 ⚡ |
용어 정리
| 개념 | 설명 |
|---|---|
| HybridObject | JS ↔ 네이티브 연결 객체 |
| Nitrogen | TypeScript → 네이티브 브릿지 자동 생성 |
| JSI | JS에서 C++ 직접 호출 (브릿지 없음) |
| Autolinking | 자동 등록 (Default constructor 필수) |
| Namespace | margelo::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() 실행 │
│ ↓ │
│ 결과 즉시 반환 │
└─────────────────────────────────┘
비교
| TurboModules | Nitro | |
|---|---|---|
| 성능 | JSI 기반 | 16배 빠름 |
| 언어 | Objective-C 필요 | Swift/Kotlin 직접 |
| 타입 안전성 | Flow 기반 | 완벽 (TS 단일 공급원) |
| 객체 | HostObject | NativeState (더 효율적) |
| 런타임 오버헤드 | 있음 | 제로 |
정리
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를 직접 지원한다.