React Native TurboModule 연결기 - iOS

“연결됨” 하나 찍기까지의 긴 여정 (RN New Architecture)
김보람's avatar
Dec 20, 2025
React Native TurboModule 연결기 - iOS

난 실직자다. 실직한김에 시간널널한김에 맨날 플젝끝내는거에 바뻐 공식문서 한번 제대로 안읽어본 New Architecture를 제대로 경험하고자한다

전에 기존 아키텍처로 브릿지 통해서 네이티브 모듈을 연결한적있지만 거의 GPT가 다했었다 이번엔 공식문서 바탕으로 해봤다

만약 누군가가 본다면 말해주고싶다 GPT를 너무 믿지마세여 우리의 버전은 미친 속도로 바뀐답니당


목표

  • TurboModule을 연동해 네이티브에서 “연결됨“이라는 텍스트 받아 RN에서 출력하기


작업 시작

1. TurboModule의 인터페이스 정의

  1. 프로젝트 루트레벨에 specs폴더 생성

  2. specs폴더 내에 NativeCamera.ts생성 (추후 카메라 모듈 붙일거라 요롷게 만듬)

//NativeCamera.ts

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

export interface Spec extends TurboModule {
  getStatus(): Promise<string>;
}
// ☝️위 부분은 "이 모듈이 어떤 API를 제공하는지"에 대한 내용이다 

export default TurboModuleRegistry.getEnforcing<Spec>('NativeCamera');
// ☝️모듈 명을 정의한다 "NativeCamera"
// Codegen에서 생성되는 파일 이름의 기준이 되고
// iOS Native에서 등록되는 모듈 이름과 매칭되고
// JS 런타임에서 네이티브 바이너리를 찾을 때 쓰인다

여기 이름이 런타임 도중 바이너리에 없으면 짤없이 크래시 난다 오늘 100번 경험함

2. 프로젝트 package.json에 “codegenConfig“ 추가

  "codegenConfig": {
    "name": "NativeCameraSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.streakly"
    }
  },

이걸 각각 노나서 보면

  • "jsSrcsDir": "specs" → codegen에게 스캔 위치 알려줌

  • "type": "modules" → 이 Spec은 TurboModule이라고 알려줌 만약type이 “components” 였다면 Fabric 컴포넌트가 된다

  • "name": "NativeCameraSpec" → 파일을 NativeCameraSpec 이 이름을 기준으로 생성해라

  • "android": {} → 지금은 iOS니까 무시(안드 필수값)
     

3. 명령어로 코드젠 실행

💡

cd ios

bundle install

bundle exec pod install

이 명령어들을 입력하면 /ios/build/generated/ios/ReactCodegen 이 경로에 아래와 같이 생성된다

  • NativeCameraSpec/NativeCameraSpec-generated.mm

  • NativeCameraSpec/NativeCameraSpec.h

    • 이 파일을 뜯어 보면 위에서 작성된 TS파일의 아래 부분이 Codegen의 규칙에 따라

      export interface Spec extends TurboModule {
        getStatus(): Promise<string>;
      }

      이렇게 생성된다 (생성된 코드의 일부분일 뿐) 즉 ts를 수정하면 다시 생성해야한다는 이야기다

      @protocol NativeCameraSpec <RCTBridgeModule, RCTTurboModule>
      
      - (void)getStatus:(RCTPromiseResolveBlock)resolve
                 reject:(RCTPromiseRejectBlock)reject;
      
      @end

4. XCode에서 네이티브 모듈 작성

  1. 네이티브 모듈용 그룹 생성

    1. 필수 아님 그냥 추후에 모듈 많이 생길거라 그룹핑함

  2. Objective-C 파일 템플릿으로 구현 파일 생성

    1. Objective-C 파일로 생성

    2. .m파일로 생성되었을 파일의 확장자를 .mm으로 변경 (필수)

      사실 이부분은 이해를 한상태는 아니다 GPT에게 물어보니
      Objective-C (.m)에서는 컴파일 불가, Obj-C++ (.mm)에서만 가능 이라고 한다

    3. .h를 작성시 위에서 코드젠이 생성한 *Spec.h 파일 임포트

      #import <NativeCameraSpec/NativeCameraSpec.h>

      JS ↔ Native 연결이 성립을 위해 꼭 필요하다.

    4. 모듈 총 코드

      //
      //  NativeCamera.h
      //  Streakly
      //
      //  Created by edint on 12/20/25.
      //
      
      #import <Foundation/Foundation.h>
      #import <NativeCameraSpec/NativeCameraSpec.h>
      
      NS_ASSUME_NONNULL_BEGIN
      
      @interface NativeCamera : NSObject <NativeCameraSpec>
      @end
      
      NS_ASSUME_NONNULL_END
      //
      //  NativeCamera.m
      //  Streakly
      //
      //  Created by edint on 12/20/25.
      //
      
      #import "NativeCamera.h"
      
      @implementation NativeCamera
      
      // Codegen이 만든 프로토콜 시그니처 그대로 구현 (Promise)
      - (void)getStatus:(RCTPromiseResolveBlock)resolve
                 reject:(RCTPromiseRejectBlock)reject
      {
        resolve(@"연결됨");
      }
      
      // TurboModule 연결 
      - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
          (const facebook::react::ObjCTurboModule::InitParams &)params
      {
        return std::make_shared<facebook::react::NativeCameraSpecJSI>(params);
      }
      
      // JS에서 getEnforcing<Spec>('NativeCamera')로 찾는 “그 이름”
      + (NSString *)moduleName
      {
        return @"NativeCamera";
      }
      
      @end
      

5. 모듈 실행! 끝

import { Button, View } from 'react-native';
import NativeCamera from '../../../specs/NativeCamera';

export const RecordScreen: React.FC = () => {
  const onPressGallery = async () => {
    try {
      const res = await NativeCamera.getStatus();
      console.log(res);
    } catch (e) {
      console.error(e);
    }
  };
  return (
    <View>
      <Button title="갤러리에서 가져오기" onPress={onPressGallery} />
      <Button title="사진 찍기" onPress={() => {}} />
    </View>
  );
};


요약 흐림도

TS Spec

→ Codegen 실행 (pod install)

→ NativeCameraSpec.h / .mm 생성

→ Xcode에서 NativeCamera.h / .mm 구현

→ TurboModuleRegistry로 JS에서 NativeCamera 호출

Share article

RN 삽질 일지