크로스 플랫폼 네이티브 모듈 (C++)
C++로 모듈을 작성하면 Android와 iOS 간에 플랫폼에 구애받지 않는 코드를 공유할 수 있다. 순수 C++ 모듈을 사용하면 로직을 한 번만 작성하고 모든 플랫폼에서 바로 재사용할 수 있다. 플랫폼별 코드를 따로 작성할 필요가 없다.
이 가이드에서는 순수 C++ Turbo 네이티브 모듈을 만드는 과정을 단계별로 설명한다:
- JS 스펙 생성
- 스캐폴딩 생성을 위해 Codegen 설정
- 네이티브 로직 구현
- Android와 iOS 애플리케이션에서 모듈 등록
- JS에서 변경 사항 테스트
이 가이드의 나머지 부분은 다음 커맨드를 실행해 애플리케이션을 생성했다고 가정한다:
npx @react-native-community/cli@latest init SampleApp --version 0.76.0
1. JS 스펙 파일 생성
순수 C++ Turbo Native Module은 Turbo Native Module이다. Codegen이 스캐폴딩 코드를 생성할 수 있도록 스펙 파일(또는 spec 파일)이 필요하다. 이 스펙 파일은 JS에서 Turbo Native Module에 접근할 때 사용한다.
스펙 파일은 타입이 지정된 JS 언어로 작성해야 한다. React Native는 현재 Flow나 TypeScript를 지원한다.
- 앱의 루트 폴더 안에
specs
라는 새 폴더를 만든다. NativeSampleModule.ts
라는 새 파일을 생성하고 다음 코드를 추가한다.
모든 Native Turbo Module 스펙 파일은 Native
접두사를 붙여야 한다. 그렇지 않으면 Codegen이 이를 무시한다.
- TypeScript
- Flow
// @flow
import type {TurboModule} from 'react-native'
import { TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
"NativeSampleModule"
): Spec);
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
2. Codegen 설정
다음 단계는 package.json
파일에서 Codegen을 설정하는 것이다. 파일을 다음과 같이 업데이트한다:
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
}
},
"dependencies": {
이 설정은 Codegen이 specs
폴더에서 스펙 파일을 찾도록 지시한다. 또한 Codegen이 modules
에 대해서만 코드를 생성하고, 생성된 코드를 AppSpecs
네임스페이스로 묶도록 설정한다.
3. 네이티브 코드 작성하기
C++ Turbo Native Module을 작성하면 Android와 iOS 간에 코드를 공유할 수 있다. 따라서 한 번만 코드를 작성하고, 플랫폼별로 어떤 변경이 필요한지 살펴보면 C++ 코드를 활용할 수 있다.
-
android
와ios
폴더와 동일한 레벨에shared
폴더를 생성한다. -
shared
폴더 안에NativeSampleModule.h
파일을 만든다.shared/NativeSampleModule.h#pragma once
#include <AppSpecsJSI.h>
#include <memory>
#include <string>
namespace facebook::react {
class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);
std::string reverseString(jsi::Runtime& rt, std::string input);
};
} // namespace facebook::react -
shared
폴더 안에NativeSampleModule.cpp
파일을 생성한다.shared/NativeSampleModule.cpp#include "NativeSampleModule.h"
namespace facebook::react {
NativeSampleModule::NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleModuleCxxSpec(std::move(jsInvoker)) {}
std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) {
return std::string(input.rbegin(), input.rend());
}
} // namespace facebook::react
이제 생성한 두 파일을 살펴보자:
NativeSampleModule.h
파일은 순수 C++ TurboModule의 헤더 파일이다.include
문은 Codegen에 의해 생성될 스펙을 포함하며, 이 스펙은 구현해야 할 인터페이스와 기본 클래스를 담고 있다.- 이 모듈은
facebook::react
네임스페이스 안에 위치해 해당 네임스페이스의 모든 타입에 접근할 수 있다. NativeSampleModule
클래스는 실제 Turbo Native Module 클래스이며,NativeSampleModuleCxxSpec
클래스를 상속받는다. 이 클래스는 Turbo Native Module로 동작할 수 있도록 하는 글루 코드와 보일러플레이트 코드를 포함한다.- 마지막으로, JS와 통신할 때 필요한
CallInvoker
에 대한 포인터를 받는 생성자와 구현해야 할 함수의 프로토타입이 있다.
NativeSampleModule.cpp
파일은 Turbo Native Module의 실제 구현을 담고 있으며, 스펙에서 선언한 생성자와 메서드를 구현한다.
4. 플랫폼에 모듈 등록하기
다음 단계에서는 플랫폼에 모듈을 등록한다. 이 과정은 네이티브 코드를 JS에 노출시켜 React Native 애플리케이션이 JS 레이어에서 네이티브 메서드를 호출할 수 있게 만드는 핵심 단계다.
이 단계는 플랫폼별 코드를 작성해야 하는 유일한 경우다.
Android
안드로이드 앱이 C++ Turbo Native Module을 효과적으로 빌드할 수 있도록 하려면 다음 단계를 따라야 한다:
- C++ 코드에 접근하기 위해
CMakeLists.txt
파일을 생성한다. - 새로 생성된
CMakeLists.txt
파일을 가리키도록build.gradle
파일을 수정한다. - 새로운 Turbo Native Module을 등록하기 위해 안드로이드 앱에
OnLoad.cpp
파일을 생성한다.
1. CMakeLists.txt
파일 생성
Android는 빌드 도구로 CMake를 사용한다. CMake가 공유 폴더에 정의한 파일에 접근하려면 해당 파일들을 빌드할 수 있어야 한다.
SampleApp/android/app/src/main/jni
폴더를 새로 만든다.jni
폴더는 Android의 C++ 코드가 위치하는 곳이다.CMakeLists.txt
파일을 생성하고 다음 내용을 추가한다:
cmake_minimum_required(VERSION 3.13)
# 라이브러리 이름을 정의한다.
project(appmodules)
# React Native 애플리케이션을 빌드하는 데 필요한 모든 것을 포함한다.
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
# 추가 소스 코드가 위치한 디렉토리를 정의한다. jni, main, src, app, android 폴더를 거슬러 올라가야 한다.
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ../../../../../shared/NativeSampleModule.cpp)
# CMake가 추가 헤더 파일을 찾을 수 있는 디렉토리를 정의한다. jni, main, src, app, android 폴더를 거슬러 올라가야 한다.
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)
이 CMake 파일은 다음과 같은 작업을 수행한다:
appmodules
라이브러리를 정의한다. 이 라이브러리에는 앱의 모든 C++ 코드가 포함된다.- React Native의 기본 CMake 파일을 로드한다.
target_sources
지시어를 사용해 빌드해야 할 모듈의 C++ 소스 코드를 추가한다. 기본적으로 React Native는appmodules
라이브러리에 기본 소스 코드를 포함시키는데, 여기서는 커스텀 소스 코드를 추가한다.jni
폴더에서 C++ Turbo Module이 위치한shared
폴더까지 거슬러 올라가는 경로를 확인할 수 있다.- CMake가 모듈 헤더 파일을 찾을 수 있는 디렉토리를 지정한다. 이 경우에도
jni
폴더에서 거슬러 올라가는 경로가 필요하다.
2. 커스텀 C++ 코드를 포함하도록 build.gradle
수정
Gradle은 안드로이드 빌드를 관리하는 도구이다. Turbo Native Module을 빌드하기 위해 CMake
파일이 위치한 경로를 Gradle에 알려줘야 한다.
SampleApp/android/app/build.gradle
파일을 연다.- 기존
android
블록 안에 다음 코드를 추가한다:
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// 주의! 프로덕션 환경에서는 자체 keystore 파일을 생성해야 한다.
// 자세한 내용은 https://reactnative.dev/docs/signed-apk-android 참고.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
+ externalNativeBuild {
+ cmake {
+ path "src/main/jni/CMakeLists.txt"
+ }
+ }
}
이 블록은 Gradle 파일에 CMakeLists.txt
파일을 찾을 위치를 알려준다. 경로는 build.gradle
파일이 있는 폴더를 기준으로 상대 경로로 지정되며, jni
폴더 내 CMakeLists.txt
파일의 경로를 추가한다.
3. 새로운 Turbo Native Module 등록하기
마지막 단계는 새로운 C++ Turbo Native Module을 런타임에 등록하는 것이다. 이렇게 하면 JS가 C++ Turbo Native Module을 요청할 때 앱이 어디서 찾아야 하는지 알고 반환할 수 있다.
SampleApp/android/app/src/main/jni
폴더에서 다음 명령어를 실행한다:
curl -O https://raw.githubusercontent.com/facebook/react-native/v0.76.0/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp
- 그런 다음, 이 파일을 다음과 같이 수정한다:
#include <DefaultComponentsRegistry.h>
#include <DefaultTurboModuleManagerDelegate.h>
#include <autolinking.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncore.h>
+ // NativeSampleModule 헤더 파일 포함
+ #include <NativeSampleModule.h>
//...
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
// 여기서 애플리케이션 또는 외부 라이브러리에서 제공하는 CXX Turbo Modules을 등록할 수 있다.
// 아래 예시는 `NativeCxxModuleExample`이라는 모듈을 등록하는 방법을 보여준다:
//
// if (name == NativeCxxModuleExample::kModuleName) {
// return std::make_shared<NativeCxxModuleExample>(jsInvoker);
// }
+ // 이 코드는 모듈을 등록하여 JS 측에서 요청할 때 앱이 반환할 수 있도록 한다.
+ if (name == NativeSampleModule::kModuleName) {
+ return std::make_shared<NativeSampleModule>(jsInvoker);
+ }
// 그리고 CXX 모듈 프로바이더를 자동으로 연결한다.
return autolinking_cxxModuleProvider(name, jsInvoker);
}
// 파일의 나머지 부분은 그대로 둔다.
이 단계들은 React Native에서 원본 OnLoad.cpp
파일을 다운로드하여, C++ Turbo Native Module을 앱에 안전하게 로드할 수 있도록 오버라이드한다.
파일을 다운로드한 후, 다음과 같이 수정한다:
- 모듈을 가리키는 헤더 파일을 포함한다.
- Turbo Native Module을 등록하여 JS가 요청할 때 앱이 반환할 수 있도록 한다.
이제 프로젝트 루트에서 yarn android
를 실행하여 앱이 성공적으로 빌드되는지 확인할 수 있다.
iOS
iOS 앱이 C++ Turbo Native Module을 효과적으로 빌드할 수 있도록 하려면 다음 단계를 따라야 한다:
- pods를 설치하고 Codegen을 실행한다.
- iOS 프로젝트에
shared
폴더를 추가한다. - 애플리케이션에서 C++ Turbo Native Module을 등록한다.
1. Pods 설치와 Codegen 실행
첫 번째 단계는 iOS 애플리케이션을 준비할 때마다 수행하는 일반적인 과정이다. CocoaPods는 React Native 의존성을 설정하고 설치하는 데 사용하는 도구이며, 이 과정의 일부로 Codegen도 자동으로 실행한다.
cd ios
bundle install
bundle exec pod install
2. iOS 프로젝트에 shared 폴더 추가하기
이 단계에서는 shared
폴더를 프로젝트에 추가해 Xcode에서 볼 수 있게 한다.
- CocoPods로 생성된 Xcode Workspace를 연다.
cd ios
open SampleApp.xcworkspace
- 왼쪽에서
SampleApp
프로젝트를 클릭하고Add files to "Sample App"...
을 선택한다.
shared
폴더를 선택하고Add
를 클릭한다.
모든 과정을 올바르게 진행했다면, 왼쪽의 프로젝트가 다음과 같이 보일 것이다:
3. 앱에 Cxx Turbo Native Module 등록하기
마지막 단계에서는 iOS 앱이 순수 C++ Turbo Native Module을 찾을 위치를 알려준다.
Xcode에서 AppDelegate.mm
파일을 열고 다음과 같이 수정한다:
#import <React/RCTBundleURLProvider.h>
+ #import <RCTAppDelegate+Protected.h>
+ #import "NativeSampleModule.h"
// ...
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
+- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
+ jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
+{
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
+
+ return [super getTurboModule:name jsInvoker:jsInvoker];
+}
@end
이 변경 사항은 몇 가지 작업을 수행한다:
RCTAppDelegate+Protected
헤더를 임포트해 AppDelegate가RCTTurboModuleManagerDelegate
프로토콜을 준수한다는 것을 알린다.- 순수 C++ Native Turbo Module 인터페이스인
NativeSampleModule.h
를 임포트한다. - C++ 모듈을 위해
getTurboModule
메서드를 오버라이드한다. 이제 JS 측에서NativeSampleModule
이라는 모듈을 요청하면, 앱이 어떤 모듈을 반환해야 하는지 알 수 있다.
이제 Xcode에서 앱을 빌드하면 성공적으로 빌드될 것이다.
5. 코드 테스트
이제 C++ Turbo Native Module을 JS에서 사용해 볼 차례다. 이를 위해 App.tsx
파일을 수정해 Turbo Native Module을 불러오고 코드에서 호출해야 한다.
App.tsx
파일을 연다.- 템플릿의 내용을 다음 코드로 대체한다:
import React from 'react';
import {
Button,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import SampleTurboModule from './specs/NativeSampleModule';
function App(): React.JSX.Element {
const [value, setValue] = React.useState('');
const [reversedValue, setReversedValue] = React.useState('');
const onPress = () => {
const revString = SampleTurboModule.reverseString(value);
setReversedValue(revString);
};
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C++ Turbo Native Module Example
</Text>
<Text>Write down here he text you want to revert</Text>
<TextInput
style={styles.textInput}
placeholder="Write your text here"
onChangeText={setValue}
value={value}
/>
<Button title="Reverse" onPress={onPress} />
<Text>Reversed text: {reversedValue}</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
marginBottom: 20,
},
textInput: {
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 10,
marginTop: 10,
},
});
export default App;
이 앱에서 주목할 만한 부분은 다음과 같다:
import SampleTurboModule from './specs/NativeSampleModule';
: 이 줄은 앱에서 Turbo Native Module을 불러온다.onPress
콜백 안의const revString = SampleTurboModule.reverseString(value);
: 이 부분은 앱에서 Turbo Native Module을 사용하는 방법을 보여준다.
이 예제를 간단하게 유지하기 위해 스펙 파일을 직접 앱에서 불러왔다. 하지만 실제로는 스펙을 감싸는 별도의 파일을 만들어 앱에서 사용하는 것이 좋다. 이렇게 하면 스펙에 대한 입력을 준비하고 JS에서 더 많은 제어를 할 수 있다.
축하한다! 첫 번째 C++ Turbo Native Module을 작성했다.
![]() | ![]() |