iOS 네이티브 모듈
Native Module과 Native Components는 기존 아키텍처에서 사용하던 안정적인 기술이다.
새로운 아키텍처가 안정화되면 앞으로 지원이 중단될 예정이다. 새로운 아키텍처에서는 Turbo Native Module과 Fabric Native Components를 사용해 유사한 결과를 달성한다.
iOS 네이티브 모듈에 오신 것을 환영합니다. 시작하기 전에 네이티브 모듈이 무엇인지 소개하는 네이티브 모듈 소개를 먼저 읽어보세요.
캘린더 네이티브 모듈 만들기
이 가이드에서는 CalendarModule
이라는 네이티브 모듈을 만들어 JavaScript에서 Apple의 캘린더 API에 접근할 수 있게 한다. 이 가이드를 마치면 JavaScript에서 CalendarModule.createCalendarEvent('저녁 파티', '우리 집');
과 같은 코드를 호출해 네이티브 메서드를 실행하고 캘린더 이벤트를 생성할 수 있다.
시작하기
React Native 애플리케이션 내의 iOS 프로젝트를 Xcode에서 열어보자. React Native 앱 내에서 iOS 프로젝트는 다음 위치에서 찾을 수 있다:

네이티브 코드를 작성할 때는 Xcode 사용을 권장한다. Xcode는 iOS 개발을 위해 설계되었으며, 이를 사용하면 코드 문법과 같은 작은 오류를 빠르게 해결할 수 있다.
커스텀 네이티브 모듈 파일 생성하기
첫 번째 단계는 메인 커스텀 네이티브 모듈 헤더와 구현 파일을 만드는 것이다. RCTCalendarModule.h
라는 새 파일을 생성한다.

그리고 다음 내용을 추가한다:
// RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end
만드는 네이티브 모듈에 맞는 이름을 사용할 수 있다. 캘린더 네이티브 모듈을 만들기 때문에 클래스 이름을 RCTCalendarModule
로 지정한다. ObjectiveC는 Java나 C++처럼 언어 수준에서 네임스페이스를 지원하지 않기 때문에, 클래스 이름 앞에 접두사를 붙이는 것이 관례다. 이는 애플리케이션 이름이나 인프라 이름의 약어일 수 있다. 이 예제에서 RCT는 React를 의미한다.
아래에서 볼 수 있듯이, CalendarModule 클래스는 RCTBridgeModule
프로토콜을 구현한다. 네이티브 모듈은 RCTBridgeModule
프로토콜을 구현하는 ObjectiveC 클래스다.
다음으로, 네이티브 모듈 구현을 시작해보자. Xcode에서 cocoa touch 클래스를 사용해 해당 구현 파일 RCTCalendarModule.m
을 같은 폴더에 생성하고 다음 내용을 포함시킨다:
// RCTCalendarModule.m
#import "RCTCalendarModule.h"
@implementation RCTCalendarModule
// RCTCalendarModule이라는 모듈을 내보내기
RCT_EXPORT_MODULE();
@end
모듈 이름 설정
현재 RCTCalendarModule.m
네이티브 모듈은 RCT_EXPORT_MODULE
매크로만 포함하고 있다. 이 매크로는 네이티브 모듈 클래스를 React Native에 등록하고 내보내는 역할을 한다. RCT_EXPORT_MODULE
매크로는 선택적 인자를 받을 수 있으며, 이 인자는 JavaScript 코드에서 모듈에 접근할 때 사용할 이름을 지정한다.
이 인자는 문자열 리터럴이 아니다. 아래 예제에서 RCT_EXPORT_MODULE(CalendarModuleFoo)
를 전달했지만, RCT_EXPORT_MODULE("CalendarModuleFoo")
는 아니다.
// CalendarModuleFoo라는 이름으로 모듈을 내보내기
RCT_EXPORT_MODULE(CalendarModuleFoo);
이렇게 하면 JavaScript에서 다음과 같이 모듈에 접근할 수 있다:
const {CalendarModuleFoo} = ReactNative.NativeModules;
이름을 지정하지 않으면 JavaScript 모듈 이름은 Objective-C 클래스 이름과 동일하게 설정되며, "RCT"나 "RK" 접두사는 제거된다.
아래 예제를 따라 RCT_EXPORT_MODULE
를 인자 없이 호출해 보자. 결과적으로 모듈은 CalendarModule
이라는 이름으로 React Native에 노출된다. 이는 Objective-C 클래스 이름에서 RCT가 제거된 이름이다.
// 이름을 전달하지 않으면 Objective-C 클래스 이름에서 “RCT”가 제거된 이름으로 모듈을 내보낸다
RCT_EXPORT_MODULE();
이렇게 하면 JavaScript에서 다음과 같이 모듈에 접근할 수 있다:
const {CalendarModule} = ReactNative.NativeModules;
네이티브 메서드를 JavaScript에 노출하기
React Native는 명시적으로 설정하지 않으면 네이티브 모듈의 메서드를 JavaScript에 노출하지 않는다. 이를 위해 RCT_EXPORT_METHOD
매크로를 사용한다. RCT_EXPORT_METHOD
매크로로 작성된 메서드는 비동기 방식으로 동작하며, 반환 타입은 항상 void이다. RCT_EXPORT_METHOD
메서드의 결과를 JavaScript에 전달하려면 콜백을 사용하거나 이벤트를 발생시킬 수 있다(이후에 다룸). 이제 RCT_EXPORT_METHOD
매크로를 사용해 CalendarModule
네이티브 모듈에 createCalendarEvent()
라는 네이티브 메서드를 설정해 보자. 현재는 문자열 타입의 name과 location 인자를 받도록 한다. 인자 타입 옵션은 잠시 후에 다룬다.
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}
TurboModules를 사용할 경우, 메서드가 RCT 인자 변환에 의존하지 않는다면
RCT_EXPORT_METHOD
매크로가 필요하지 않다(아래 인자 타입 참조). 결국 React Native는RCT_EXPORT_MACRO
를 제거할 예정이므로RCTConvert
사용을 권장하지 않는다. 대신 메서드 본문 내에서 인자 변환을 수행할 수 있다.
createCalendarEvent()
메서드의 기능을 구현하기 전에, React Native 애플리케이션에서 JavaScript로부터 호출되었는지 확인할 수 있도록 콘솔 로그를 추가한다. React의 RCTLog
API를 사용한다. 파일 상단에 해당 헤더를 임포트한 후 로그 호출을 추가한다.
#import <React/RCTLog.h>
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
동기식 메서드
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
를 사용하면 동기식 네이티브 메서드를 만들 수 있다.
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}
이 메서드의 반환 타입은 객체 타입(id)이어야 하며 JSON으로 직렬화 가능해야 한다. 따라서 이 메서드는 nil 또는 JSON 값(예: NSNumber, NSString, NSArray, NSDictionary)만 반환할 수 있다.
현재는 동기식 메서드 사용을 권장하지 않는다. 동기식 메서드를 호출하면 성능에 큰 영향을 미칠 수 있고, 네이티브 모듈에서 스레드 관련 버그가 발생할 가능성이 높기 때문이다. 또한 RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
를 사용하기로 결정하면, 더 이상 Google Chrome 디버거를 사용할 수 없다. 동기식 메서드는 JS VM이 앱과 메모리를 공유해야 하기 때문이다. Google Chrome 디버거의 경우, React Native는 Google Chrome 내부의 JS VM에서 실행되며 WebSockets를 통해 모바일 장치와 비동기적으로 통신한다.
이제 iOS 네이티브 모듈의 기본 구조를 설정했다. 이제 네이티브 모듈에 접근하고 자바스크립트에서 내보낸 메서드를 호출해 테스트해 보자.
앱에서 네이티브 모듈의 createCalendarEvent()
메서드를 호출할 위치를 찾는다. 아래는 앱에 추가할 수 있는 NewModuleButton
컴포넌트 예제다. NewModuleButton
의 onPress()
함수 내에서 네이티브 모듈을 호출할 수 있다.
import React from 'react';
import {Button} from 'react-native';
const NewModuleButton = () => {
const onPress = () => {
console.log('We will invoke the native module here!');
};
return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};
export default NewModuleButton;
자바스크립트에서 네이티브 모듈에 접근하려면 먼저 React Native의 NativeModules
를 가져와야 한다.
import {NativeModules} from 'react-native';
그런 다음 NativeModules
에서 CalendarModule
네이티브 모듈에 접근할 수 있다.
const {CalendarModule} = NativeModules;
이제 CalendarModule
네이티브 모듈을 사용할 수 있으므로 네이티브 메서드 createCalendarEvent()
를 호출할 수 있다. 아래는 NewModuleButton
의 onPress()
메서드에 이를 추가한 예제다.
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};
마지막 단계는 React Native 앱을 다시 빌드하여 최신 네이티브 코드(새로운 네이티브 모듈 포함)를 사용할 수 있게 하는 것이다. React Native 애플리케이션이 위치한 커맨드라인에서 다음 명령어를 실행한다.
- npm
- Yarn
npm run ios
yarn ios
반복하며 빌드하기
이 가이드를 따라 네이티브 모듈을 반복적으로 개선해 나가면서, 가장 최근에 변경한 내용을 자바스크립트에서 접근하려면 애플리케이션의 네이티브 부분을 다시 빌드해야 한다. 작성하는 코드가 애플리케이션의 네이티브 영역에 위치하기 때문이다. React Native의 메트로 번들러는 자바스크립트 파일의 변경을 감지하고 즉시 JS 번들을 다시 빌드할 수 있지만, 네이티브 코드에는 이를 적용하지 않는다. 따라서 최신 네이티브 변경 사항을 테스트하려면 위 명령어를 사용해 다시 빌드해야 한다.
이제 여러분은 JavaScript에서 createCalendarEvent()
메서드를 네이티브 모듈에 호출할 수 있다. 함수 내에서 RCTLog
를 사용하고 있으므로, 앱에서 디버그 모드를 활성화하고 Chrome의 JS 콘솔이나 모바일 앱 디버거 Flipper를 확인하면 네이티브 메서드가 호출되는 것을 확인할 수 있다. 네이티브 모듈 메서드를 호출할 때마다 RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
메시지가 표시된다.

여러분은 이제 iOS 네이티브 모듈을 생성하고 React Native 애플리케이션의 JavaScript에서 해당 메서드를 호출했다. 다음으로 네이티브 모듈 메서드가 받는 인자 타입과 콜백 및 Promise를 설정하는 방법에 대해 더 알아볼 수 있다.
캘린더 네이티브 모듈의 확장
더 나은 네이티브 모듈 내보내기
위에서처럼 NativeModules
에서 네이티브 모듈을 가져오는 방식은 다소 번거롭다. 네이티브 모듈을 사용할 때마다 이 작업을 반복하지 않도록, JavaScript 래퍼를 만들어 사용하면 편리하다. NativeCalendarModule.js라는 새로운 JavaScript 파일을 만들고 다음과 같이 작성한다:
/**
* 이 코드는 네이티브 CalendarModule 모듈을 JS 모듈로 노출한다. 여기에는 다음 매개변수를 받는 'createCalendarEvent' 함수가 있다:
*
* 1. String name: 이벤트 이름을 나타내는 문자열
* 2. String location: 이벤트 장소를 나타내는 문자열
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;
이 JavaScript 파일은 JavaScript 측 기능을 추가하기에도 적합하다. 예를 들어, TypeScript와 같은 타입 시스템을 사용한다면 여기에 네이티브 모듈에 대한 타입 주석을 추가할 수 있다. React Native가 아직 Native to JS 타입 안전성을 지원하지 않지만, 이러한 타입 주석을 통해 모든 JS 코드를 타입 안전하게 만들 수 있다. 또한 이 주석은 나중에 타입 안전한 네이티브 모듈로 전환하는 데도 도움이 된다. 아래는 Calendar Module에 타입 안전성을 추가한 예시다:
/**
* 이 코드는 네이티브 CalendarModule 모듈을 JS 모듈로 노출한다. 여기에는 다음 매개변수를 받는 'createCalendarEvent' 함수가 있다:
*
* 1. String name: 이벤트 이름을 나타내는 문자열
* 2. String location: 이벤트 장소를 나타내는 문자열
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
interface CalendarInterface {
createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;
다른 JavaScript 파일에서 네이티브 모듈에 접근하고 메서드를 호출하려면 다음과 같이 작성한다:
import NativeCalendarModule from './NativeCalendarModule';
NativeCalendarModule.createCalendarEvent('foo', 'bar');
참고: 이 예제는
CalendarModule
을 가져오는 위치가NativeCalendarModule.js
와 동일한 계층 구조에 있다고 가정한다. 필요에 따라 상대 경로를 업데이트해야 한다.
인자 타입
자바스크립트에서 네이티브 모듈 메서드를 호출할 때, React Native는 인자를 JS 객체에서 해당하는 Objective-C/Swift 객체로 변환한다. 예를 들어, Objective-C 네이티브 모듈 메서드가 NSNumber를 받는다면, JS에서는 숫자로 메서드를 호출해야 한다. React Native가 이 변환을 자동으로 처리한다. 아래는 네이티브 모듈 메서드에서 지원하는 인자 타입과 그에 대응하는 자바스크립트 타입을 정리한 표다.
Objective-C | JavaScript |
---|---|
NSString | string, ?string |
BOOL | boolean |
double | number |
NSNumber | ?number |
NSArray | Array, ?Array |
NSDictionary | Object, ?Object |
RCTResponseSenderBlock | Function (success) |
RCTResponseSenderBlock, RCTResponseErrorBlock | Function (failure) |
RCTPromiseResolveBlock, RCTPromiseRejectBlock | Promise |
아래 타입들은 현재 지원되지만 TurboModules에서는 지원되지 않을 예정이다. 사용을 피하는 것이 좋다.
- Function (failure) -> RCTResponseErrorBlock
- Number -> NSInteger
- Number -> CGFloat
- Number -> float
iOS의 경우, RCTConvert
클래스에서 지원하는 모든 인자 타입으로 네이티브 모듈 메서드를 작성할 수 있다. (자세한 내용은 RCTConvert 참조) RCTConvert 헬퍼 함수들은 모두 JSON 값을 입력으로 받아 네이티브 Objective-C 타입이나 클래스로 매핑한다.
상수 내보내기
네이티브 모듈은 constantsToExport()
메서드를 오버라이드하여 상수를 내보낼 수 있다. 아래 예제에서는 constantsToExport()
를 오버라이드하고, 자바스크립트에서 접근할 수 있는 기본 이벤트 이름 프로퍼티를 포함한 딕셔너리를 반환한다.
- (NSDictionary *)constantsToExport
{
return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
}
이 상수는 자바스크립트에서 네이티브 모듈의 getConstants()
를 호출하여 접근할 수 있다.
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);
기술적으로 constantsToExport()
로 내보낸 상수는 NativeModule
객체에서 직접 접근할 수 있다. 하지만 TurboModules에서는 더 이상 이 방식을 지원하지 않으므로, 추후 마이그레이션을 피하기 위해 위의 방식을 사용할 것을 권장한다.
상수는 초기화 시점에만 내보내지므로, 런타임에
constantsToExport()
의 값을 변경해도 자바스크립트 환경에는 영향을 미치지 않는다.
iOS의 경우 constantsToExport()
를 오버라이드하면 + requiresMainQueueSetup
도 구현해야 한다. 이 메서드는 자바스크립트 코드가 실행되기 전에 모듈이 메인 스레드에서 초기화되어야 하는지 여부를 React Native에 알려준다. 이 메서드를 구현하지 않으면 추후 모듈이 백그라운드 스레드에서 초기화될 수 있다는 경고가 표시된다. 모듈이 UIKit에 접근할 필요가 없다면 + requiresMainQueueSetup
에 NO를 반환해야 한다.
콜백
네이티브 모듈은 특별한 종류의 인자인 콜백도 지원한다. 콜백은 비동기 메서드를 위해 Objective-C에서 JavaScript로 데이터를 전달하는 데 사용된다. 또한 네이티브 측에서 JavaScript 코드를 비동기적으로 실행하는 데에도 활용할 수 있다.
iOS에서는 콜백이 RCTResponseSenderBlock
타입으로 구현된다. 아래 예제에서 createCalendarEventMethod()
에 콜백 파라미터 myCallBack
을 추가한다:
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
location:(NSString *)location
myCallback:(RCTResponseSenderBlock)callback)
그런 다음 네이티브 함수 내에서 콜백을 호출하며, JavaScript로 전달할 결과를 배열 형태로 제공한다. RCTResponseSenderBlock
은 단 하나의 인자만을 받는다는 점에 유의하라. 이 인자는 JavaScript 콜백에 전달될 파라미터들의 배열이다. 아래 예제에서는 앞서 호출에서 생성된 이벤트의 ID를 전달한다.
콜백은 네이티브 함수가 완료된 직후에 즉시 호출되지 않는다는 점을 강조해야 한다. 통신이 비동기적으로 이루어지기 때문이다.
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
NSInteger eventId = ...
callback(@[@(eventId)]);
RCTLogInfo(@"Pretending to create an event %@ at %@", title, location);
}
이 메서드는 JavaScript에서 다음과 같이 접근할 수 있다:
const onSubmit = () => {
CalendarModule.createCalendarEvent(
'Party',
'04-12-2020',
eventId => {
console.log(`Created a new event with id ${eventId}`);
},
);
};
네이티브 모듈은 콜백을 단 한 번만 호출해야 한다. 그러나 콜백을 저장해두고 나중에 호출할 수도 있다. 이 패턴은 델리게이트가 필요한 iOS API를 래핑할 때 자주 사용된다. 예를 들어 RCTAlertManager
를 참고하라. 만약 콜백이 호출되지 않으면 메모리 누수가 발생할 수 있다.
콜백을 사용한 에러 처리에는 두 가지 방법이 있다. 첫 번째는 Node의 관례를 따르는 것으로, 콜백 배열의 첫 번째 인자를 에러 객체로 취급하는 방식이다.
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
NSNumber *eventId = [NSNumber numberWithInt:123];
callback(@[[NSNull null], eventId]);
}
JavaScript에서는 첫 번째 인자를 확인해 에러가 전달되었는지 확인할 수 있다:
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};
다른 방법은 두 개의 별도 콜백을 사용하는 것이다: onFailure와 onSuccess.
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title
location:(NSString *)location
errorCallback: (RCTResponseSenderBlock)errorCallback
successCallback: (RCTResponseSenderBlock)successCallback)
{
@try {
NSNumber *eventId = [NSNumber numberWithInt:123];
successCallback(@[eventId]);
}
@catch ( NSException *e ) {
errorCallback(@[e]);
}
}
그런 다음 JavaScript에서 에러와 성공 응답을 위한 별도 콜백을 추가할 수 있다:
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};
JavaScript에 에러와 유사한 객체를 전달하려면 RCTUtils.h.
의 RCTMakeError
를 사용한다. 현재 이 함수는 단순히 Error 형태의 딕셔너리를 JavaScript로 전달하지만, React Native는 앞으로 실제 JavaScript Error 객체를 자동으로 생성할 계획이다. 또한 RCTResponseErrorBlock
인자를 제공할 수도 있다. 이 인자는 에러 콜백에 사용되며 NSError \* 객체
를 받는다. 단, 이 인자 타입은 TurboModules에서 지원되지 않는다는 점에 유의하라.
Promise 활용
네이티브 모듈은 Promise를 처리할 수 있다. 이 기능은 특히 ES2016의 async/await
문법을 사용할 때 자바스크립트 코드를 간소화하는 데 유용하다. 네이티브 모듈 메서드의 마지막 매개변수가 RCTPromiseResolveBlock
과 RCTPromiseRejectBlock
일 때, 해당 JS 메서드는 JS Promise 객체를 반환한다.
콜백 대신 Promise를 사용하도록 위의 코드를 리팩토링하면 다음과 같다:
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSInteger eventId = createCalendarEvent();
if (eventId) {
resolve(@(eventId));
} else {
reject(@"event_failure", @"no event id returned", nil);
}
}
이 메서드의 자바스크립트 버전은 Promise를 반환한다. 따라서 async
함수 내에서 await
키워드를 사용해 호출하고 결과를 기다릴 수 있다:
const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'my house',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};
JavaScript로 이벤트 전송하기
네이티브 모듈은 직접 호출되지 않아도 JavaScript로 이벤트를 신호할 수 있다. 예를 들어, 네이티브 iOS 캘린더 앱의 캘린더 이벤트가 곧 발생할 것임을 JavaScript에 알리고 싶을 수 있다. 이를 구현하는 가장 좋은 방법은 RCTEventEmitter
를 서브클래싱하고, supportedEvents
를 구현한 후 self sendEventWithName
을 호출하는 것이다.
헤더 클래스를 업데이트하여 RCTEventEmitter
를 임포트하고 RCTEventEmitter
를 서브클래싱한다:
// CalendarModule.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarModule : RCTEventEmitter <RCTBridgeModule>
@end
JavaScript 코드는 모듈 주변에 새로운 NativeEventEmitter
인스턴스를 생성하여 이러한 이벤트를 구독할 수 있다.
리스너가 없는 상태에서 이벤트를 발생시키면 불필요한 리소스가 소모된다는 경고를 받을 수 있다. 이를 방지하고 모듈의 작업 부하를 최적화하기 위해(예: 업스트림 알림 구독 취소 또는 백그라운드 작업 일시 중지), RCTEventEmitter
서브클래스에서 startObserving
과 stopObserving
을 오버라이드할 수 있다.
@implementation CalendarModule
{
bool hasListeners;
}
// 이 모듈의 첫 번째 리스너가 추가될 때 호출된다.
-(void)startObserving {
hasListeners = YES;
// 필요한 경우 업스트림 리스너 또는 백그라운드 작업을 설정한다.
}
// 이 모듈의 마지막 리스너가 제거되거나 할당 해제될 때 호출된다.
-(void)stopObserving {
hasListeners = NO;
// 업스트림 리스너를 제거하고 불필요한 백그라운드 작업을 중지한다.
}
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
if (hasListeners) {// 리스너가 있을 때만 이벤트를 전송한다.
[self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}
}
스레딩
네이티브 모듈이 자체 메서드 큐를 제공하지 않는다면, 어떤 스레드에서 호출되는지에 대해 가정하지 않아야 한다. 현재는 네이티브 모듈이 메서드 큐를 제공하지 않으면, React Native가 별도의 GCD 큐를 생성하고 그곳에서 메서드를 호출한다. 이는 구현 세부사항이며 변경될 수 있음을 유의해야 한다. 네이티브 모듈에 명시적으로 메서드 큐를 제공하려면, (dispatch_queue_t) methodQueue
메서드를 오버라이드해야 한다. 예를 들어, 메인 스레드에서만 사용해야 하는 iOS API를 사용해야 한다면 다음과 같이 지정할 수 있다.
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
마찬가지로, 어떤 작업이 완료되는 데 오랜 시간이 걸릴 수 있다면, 네이티브 모듈은 자체 큐를 지정하여 작업을 실행할 수 있다. 다시 말하지만, 현재 React Native는 네이티브 모듈에 별도의 메서드 큐를 제공하지만, 이는 의존해서는 안 되는 구현 세부사항이다. 자체 메서드 큐를 제공하지 않으면, 미래에 네이티브 모듈의 장기 실행 작업이 다른 관련 없는 네이티브 모듈에서 실행되는 비동기 호출을 블로킹할 수 있다. 예를 들어, RCTAsyncLocalStorage
모듈은 자체 큐를 생성하여 React 큐가 잠재적으로 느린 디스크 접근을 기다리며 블로킹되지 않도록 한다.
- (dispatch_queue_t)methodQueue
{
return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}
지정된 methodQueue
는 모듈의 모든 메서드에서 공유된다. 만약 하나의 메서드만 장기 실행 작업이 필요하거나 (또는 다른 이유로 다른 큐에서 실행되어야 한다면), dispatch_async
를 사용하여 특정 메서드의 코드를 다른 큐에서 실행할 수 있다. 이렇게 하면 다른 메서드에 영향을 주지 않는다.
RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 백그라운드 스레드에서 장기 실행 코드 호출
...
// 어떤 스레드/큐에서든 콜백 호출 가능
callback(@[...]);
});
}
모듈 간 디스패치 큐 공유
methodQueue
메서드는 모듈이 초기화될 때 한 번 호출되며, React Native에 의해 유지된다. 따라서 모듈 내에서 사용하지 않는다면 큐에 대한 참조를 유지할 필요가 없다. 하지만 여러 모듈 간에 동일한 큐를 공유하려면, 각 모듈에 대해 동일한 큐 인스턴스를 유지하고 반환해야 한다.
의존성 주입(Dependency Injection)
React Native는 등록된 네이티브 모듈을 자동으로 생성하고 초기화한다. 하지만 의존성을 주입하기 위해 직접 모듈 인스턴스를 생성하고 초기화하고 싶을 때가 있다.
이를 위해 RCTBridgeDelegate
프로토콜을 구현한 클래스를 만들고, 이 클래스의 인스턴스를 인자로 RCTBridge
를 초기화한 후, 초기화된 브리지를 사용해 RCTRootView
를 생성할 수 있다.
id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:kModuleName
initialProperties:nil];
Swift 모듈 내보내기
Swift는 매크로를 지원하지 않기 때문에 React Native 내에서 네이티브 모듈과 그 메서드를 JavaScript로 노출하려면 약간의 추가 설정이 필요하다. 하지만 기본적인 방식은 거의 동일하다. 예를 들어, 동일한 CalendarModule
을 Swift 클래스로 구현한다고 가정해 보자:
// CalendarModule.swift
@objc(CalendarModule)
class CalendarModule: NSObject {
@objc(addEvent:location:date:)
func addEvent(_ name: String, location: String, date: NSNumber) -> Void {
// Date를 사용할 준비가 되었다!
}
@objc
func constantsToExport() -> [String: Any]! {
return ["someKey": "someValue"]
}
}
클래스와 함수가 Objective-C 런타임에 제대로 노출되도록 하려면
@objc
수정자를 반드시 사용해야 한다.
그 다음, React Native에 필요한 정보를 등록할 private 구현 파일을 생성한다:
// CalendarModuleBridge.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(CalendarModule, NSObject)
RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)
@end
Swift와 Objective-C를 처음 접하는 개발자라면, iOS 프로젝트에서 두 언어를 혼합할 때 Swift 파일에 Objective-C 파일을 노출하기 위해 추가로 브리징 헤더 파일이 필요하다. Xcode에서 File>New File
메뉴 옵션을 통해 Swift 파일을 앱에 추가하면 Xcode가 이 헤더 파일을 생성하도록 제안한다. 이 헤더 파일에 RCTBridgeModule.h
를 임포트해야 한다.
// CalendarModule-Bridging-Header.h
#import <React/RCTBridgeModule.h>
RCT_EXTERN_REMAP_MODULE
과 _RCT_EXTERN_REMAP_METHOD
를 사용해 내보내는 모듈이나 메서드의 JavaScript 이름을 변경할 수도 있다. 자세한 내용은 RCTBridgeModule
을 참고한다.
서드파티 모듈을 만들 때 주의할 점: Swift를 사용하는 정적 라이브러리는 Xcode 9 이상에서만 지원된다. 모듈에 포함된 iOS 정적 라이브러리에서 Swift를 사용할 때 Xcode 프로젝트가 빌드되려면, 메인 앱 프로젝트에 Swift 코드와 브리징 헤더가 반드시 포함되어야 한다. 앱 프로젝트에 Swift 코드가 없다면, 빈 .swift 파일과 빈 브리징 헤더를 추가하는 방식으로 해결할 수 있다.
예약된 메서드 이름
invalidate()
네이티브 모듈은 iOS에서 invalidate()
메서드를 구현해 RCTInvalidating 프로토콜을 준수할 수 있다. 이 메서드는 네이티브 브릿지가 무효화될 때(예: 개발 모드에서 리로드 시) 호출될 수 있다. 네이티브 모듈에 필요한 정리 작업을 수행하려면 이 메커니즘을 적절히 활용한다.