Bridge는 React Native의 아키텍처 컴포넌트로, JavaScript 스레드와 iOS 및 Android의 네이티브 환경 사이에 비동기 통신을 제공합니다. 이는 큐를 통해 직렬화된 JSON 메시지를 전송하여 JS 코드에서 네이티브 API를 호출할 수 있게 합니다. Meta, 2024에 따르면, Bridge는 기존 애플리케이션의 기반으로 남아있지만, JSI 기반의 새 아키텍처에 비해 성능이 뒤떨어집니다.
주요 포인트
Bridge는 React Native의 중요한 아키텍처 요소로, 애플리케이션의 비즈니스 로직이 실행되는 JavaScript 스레드와 네이티브 iOS 및 Android 스레드 사이에 양방향 비동기 통신을 제공합니다. 2015년 React Native가 출시된 이래로 Bridge는 JS 코드가 플랫폼 API — 카메라, 위치 정보, 파일 시스템, 알림 및 기타 네이티브 기능 — 와 상호작용하는 유일한 방법으로 남아있습니다.
Bridge 아키텍처는 메시지 큐(message queue) 원리를 기반으로 합니다. JavaScript 코드가 네이티브 메소드를 호출하면, 요청이 JSON 문자열로 직렬화되고, 큐에 넣히며, 비동기적으로 네이티브 쪽으로 보내집니다. 네이티브 코드는 요청을 처리하고, 해당 작업을 수행한 다음, 결과를 같은 큐를 통해 JS 스레드로 보냅니다. React Conf 2021에서의 Meta 보고서에 따르면, 일반적인 애플리케이션에서 초당 최대 10,000개의 메시지가 Bridge를 통과합니다.
Bridge 작동에 관여하는 주요 스레드: JavaScript Thread(JS 코드 실행), Native Thread(네이티브 작업 실행) 그리고 Shadow Thread(Yoga를 사용한 레이아웃 계산)입니다. 각 스레드는 독립적으로 작동하여 UI의 응답성을 보장합니다 — 네이티브 애니메이션이 JS 계산에 의해 차단되지 않습니다.
Bridge는 통신을 위해 세 가지 주요 메커니즘을 사용합니다: MessageQueue, JSON 직렬화 그리고 메시지 배칭. MessageQueue는 React Native의 내부 컴포넌트로, JS와 네이티브 쪽 사이의 호출 큐를 관리합니다. 각 네이티브 메소드 호출은 큐에 넣히고, 직렬화된 다음 성능 최적화를 위해 배치로 보내집니다.
MessageQueue는 배칭 원리로 작동합니다: 네이티브 메소드 호출이 축적되고 5–15 밀리초마다 단일 그룹(배치)으로 보내집니다. 이로써 여러 호출이 하나의 JSON 패키지에 패키지되므로 직렬화 오버헤드가 줄어듭니다. 네이티브 쪽에서는 메시지가 디직렬화되고 해당 모듈에 분배됩니다.
네이티브 모듈은 매크로 또는 어노테이션을 통해 자동으로 등록됩니다. iOS는 RCT_EXPORT_MODULE 매크로를 사용하고, Android는 @ReactMethod 어노테이션을 사용합니다. React Native는 애플리케이션 시작 시 등록된 모듈을 스캔하여 모든 사용 가능한 메소드의 구성 JSON 맵을 빌드합니다. 이 맵은 JS 환경에 전달되고, JavaScript는 어떤 메소드가 호출 가능한지 알게 됩니다.
데이터는 다음 경로를 따릅니다: JavaScript가 NativeModules.CalendarModule.createCalendarEvent()를 호출합니다. 메소드는 모듈 식별자, 메소드 이름 및 인수를 포함한 JSON 메시지로 직렬화됩니다. 메시지는 MessageQueue에 입장합니다. 네이티브 스레드에서 메시지가 디직렬화되고 해당 모듈로 전달됩니다. 실행 결과가 다시 직렬화되어 Promise 또는 callback으로 JS 스레드에 전송됩니다.
// JavaScript에서 Bridge를 통한 네이티브 모듈 호출
import { NativeModules } from 'react-native';
const CalendarModule = NativeModules.CalendarModule;
CalendarModule.createCalendarEvent('Test Event', 'Office')
.then(eventId => {
console.log('다음 ID로 이벤트가 생성되었습니다:', eventId);
})
.catch(error => {
console.error('실패:', error);
});
네이티브 iOS 쪽에서 모듈은 RCT_EXPORT_MODULE 매크로가 있는 Objective-C 클래스로 나타납니다. 메소드는 RCT_EXPORT_METHOD 매크로를 사용하여 export되고, React Native가 자동으로 Bridge에 등록합니다. 인수는 위치로 전달되며 지원되는 JSON 타입(NSString, NSNumber, NSArray, NSDictionary, BOOL)에 해당해야 합니다.
// Bridge에서 iOS 네이티브 모듈 등록
@interface CalendarModule () RCT_EXPORT_MODULE()
@end
@implementation CalendarModule
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSNumber *eventId = createEvent(name, location);
resolve(eventId);
}
@end
Bridge에는 몇 가지 근본적인 성능 제한사항이 있습니다. 가장 주요한 것은 강제적인 비동기성과 직렬화입니다. 각 네이티브 메소드 호출은 데이터를 JSON 문자열로 변환하여 지연이 발생하고 메모리를 소비합니다. 이미지 처리 또는 비디오 작업과 같이 대량 데이터를 다루는 작업에서는 이것이 블록이 됩니다.
JSON 직렬화와 디직렬화는 CPU 시간과 메모리를 소비합니다. 각 메시지는 JS 쪽에서 문자열로 변환되고, 브리지를 통해 전송된 다음 네이티브 쪽에서 파싱되어야 합니다. Callstack 테스트(2022)에 따르면, Bridge를 통해 10,000개의 숫자 배열을 직렬화하는 데 약 30–50 밀리초가 걸리며, 고빈도 호출에는 적합하지 않습니다.
Bridge는 큰 바이너리 데이터 전송에 최적화되어 있지 않습니다. 사진, 오디오 파일 및 비디오 스트림은 대체 접근법이 필요합니다 — 예를 들어, 파일을 디스크에 쓰고 경로를 문자열로 전달하는 방식입니다. 이로 인해 파일 시스템 읽기 및 쓰기 작업에 추가 오버헤드가 발생합니다.
이러한 제한사항을 인식한 Meta 팀은 Bridge를 JSI(JavaScript Interface)와 Turbo Module로 대체하는 새 React Native 아키텍처를 개발했습니다. JSI는 직렬화 없이 네이티브 메소드를 직접 호출할 수 있어 Bridge의 주요 단점을 제거합니다.
Bridge와 Turbo Module의 비교는 아키텍처 접근법의 근본적인 차이를 보여줍니다. Bridge는 JSON 직렬화를 사용하는 비동기 메시지 큐를 사용하는 반면, Turbo Module은 JSI를 통해 작동합니다. JSI는 JavaScript와 C++ 사이의 직접 인터페이스로, 데이터 변환 없이 네이티브 메소드를 동기적으로 호출할 수 있습니다.
| 특징 | Bridge | Turbo Module |
|---|---|---|
| 호출 유형 | 비동기 | 동기 및 비동기 |
| 직렬화 | 호출마다 JSON | 복사 없는 JSI 객체 |
| 성능 | 중간 | 높음 |
| 타임 시스템 | 동적 | 정적 (Codegen) |
| 로딩 | 시작 시 모든 모듈 | 지연 (요청 시) |
Bridge와 Turbo Module 사이의 선택은 React Native 버전에 따라 달라집니다. React Native 0.72 및 그 이전 프로젝트에서는 Bridge가 주요 메커니즘으로 남아있습니다. React Native 0.73부터는 Metro와 새 아키텍처가 병렬로 지원되어 점진적 마이그레이션이 가능합니다. Turbo Module로의 완전한 전환은 React Native 0.76+로 업데이트하고 구성에서 새 아키텍처를 활성화해야 합니다.
캘린더 모듈을 예로 Bridge를 통한 Native Module 작성 및 사용의 전체 사이클을 살펴보겠습니다. 모듈은 이벤트를 생성하고 그 식별자를 반환합니다. 이 예시는 두 플랫폼 — iOS 및 Android의 설정을 모두 다룹니다.
Android에서 Native Module은 ReactContextBaseJavaModule을 확장하는 Java 클래스로 작성됩니다. @ReactMethod 어노테이션이 메소드를 Bridge로 export합니다. Promise의 경우 com.facebook.react.bridge의 Promise 인터페이스가 사용됩니다.
public class CalendarModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "CalendarModule";
}
@ReactMethod
public void createCalendarEvent(
String name,
String location,
Promise promise) {
try {
Integer eventId = createCalendarEventNative(name, location);
promise.resolve(eventId);
} catch (Exception e) {
promise.reject("EVENT_ERROR", e.getMessage());
}
}
}
모듈은 @ReactModule을 통해 또는 애플리케이션 패키지에서 수동으로 등록됩니다. React Native가 자동으로 감지하여 Bridge에 추가합니다. 등록 후 모듈은 NativeModules를 통해 JavaScript에서 액세스 가능합니다.
public class CalendarPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new CalendarModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
새 모듈을 추가할 때 Bridge는 애플리케이션의 재시작이 필요하다는 점을 주목하는 것이 중요합니다. 구성 맵이 초기화 시 한 번만 빌드되기 때문입니다. 이것이 지연 로딩되고 재시작 없이 모듈의 핫 리로드를 지원하는 Turbo Module과의 차이점입니다.
자주 묻는 질문
Bridge는 항상 비동기 큐와 JSON 직렬화를 사용하는 반면, JSI를 통한 직접 전송은 동기적으로 데이터 복사 없이 작동합니다. Bridge는 직렬화 지연을 만들지만 스레드 격리를 보장합니다.
아니오. Bridge는 비동기 호출만 지원합니다. 동기 상호작용에는 JSI와 Turbo Module이 있는 새 아키텍처가 필요합니다. 이것은 React Native 0.76+에서 해결된 주요 제한 중 하나입니다.
Bridge는 JSON으로 직렬화 가능한 타입을 지원합니다: 문자열, 숫자, 불리언 값, 배열, 사전(객체)입니다. 이미지와 같은 바이너리 데이터는 파일 시스템 또는 base64 인코딩을 통해 전송해야 합니다.
측정을 위해 React DevTools와 React Native 프로파일러를 사용하세요. Performance 탭에서 Bridge 큐의 메시지 수와 지연을 확인할 수 있습니다. 트래픽 모니터링을 위한 react-native-bridge-spy 패키지도 사용 가능합니다.
성능이 요구되는 프로젝트나 React Native 0.76+에서 새 애플리케이션을 만들 때 전환을 권장합니다. 기존 프로젝트의 경우 마이그레이션은 점진적으로 할 수 있습니다 — 두 아키텍처 모두 병렬로 작동합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.