brunch

매거진 React Native

You can make anything
by writing

C.S.Lewis

by 장영석 Aug 17. 2018

React Native

Native Modules for iOS

Native Modules

앱은 때로 플랫폼 API 접근이 필요하고, React Native에는 아직 그에 해당하는 모듈이 없다. 아마도 이미 구현되어있는 Objective-C, Swfit 또는 C++ 코드를 자바스크립트로 다시 재구현 없이 재사용하고 싶거나, 이미지 프로세싱, 데이터베이스 등에서 고성능, 멀티 스레드 코드를 작성하고 싶을 것이다.


우리는 React Native가 실제 네이티브 코드를 작성하고 플랫폼의 모든 것에 접근이 가능하게 설계했다. 이 부분은 좀 더 발전된 기능이고 일반적인 개발 프로세스라고 기대하지는 않지만, 필수적인 요소라고 생각한다. React Native가 필요한 네이티브 기능을 지원하지 않는다면, 직접 구축할 수 있어야 한다.


이것은 네이티브 모듈을 작성하는 방법을 보여주는 좀 더 고급진 가이드다. 이 글을 읽는 사람이 Objective_C나 Swift, 코어 라이브러리(Foundation UIKit)를 알고 있다고 가정한다.


Native Module Setup

네이티브 모듈은 보통 npm 패키지들로 배포되어있고 Xcode 라이브러리 프로젝트를 포함할 것이다. 기본적인 배경지식을 가지려면 Native Modules Setup 가이드를 먼저 읽어라.


iOS Calendar Moduel Example


이 가이드는 iOS Calendar API 예제를 사용할 것이다. 우리가 자바스크립트로 iOS calender를 접근하길 원한다고 가정해보자.


네이티브 모듈은 단순히 RCTBridgeModule 프로토콜을 구현한 Objective-C 클래스다. 여기서 RCT가 궁금하다면, ReactCT의 약어다.

RCTBridgeModule 프로토콜을 구현하기 위해 추가적으로, 클래스는 RCT_EXPORT_MODULE() 매크로도 포함해야만 한다. 이것은 자바스크립트 코드에서 모듈이 접근 가능한 이름을 지정하는 선택적인 인수를 갖는다(자세한 내용은 후에). 이름을 지정하지 않는다면, 자바스크립트 모듈 이름은 Objective-C 클래스 이름과 일치된다. Objective-C 클래스 이름이 RCT로 시작한다면, 자바스크립트 모듈 이름에 접두어 RCT가 제외될 것이다.

React Native는 명시적으로 작성되지 않는 한 자바스크립트를 통해 CalendarManager의 어떤 메서드도 노출하지 않을 것이다. 이 작업은 RCT_EXPORT_METHOD 매크로를 사용한다.

이제, 자바스크립트 파일로부터 이와 같이 메서드를 호출할 수 있다.


NOTE: 자바스크립트 메서드 이름

자바스크립트로 내보낸 메서드의 이름은 첫 번째 콜론까지 네이티브 메서드 이름이다. 또한 React Native는 자바스크립트 메서드의 이름을 지정하기 위해 RCT_REMAP_METHOD() 매크로를 정의한다. 이것은 여러 개의 네이티브 메서드가 첫 번째 콜론까지 같을 경우와 자바스크립트 이름과 충돌할 때 유용하다.


CalenderMaanger 모듈은 [CalendarManager new] 호출을 사용하여 Objective-C로 인스턴스화 된다. bridge 메서드의 리턴 타입은 항상 void다. React Native bridge는 비동기라서, 자바스크립트에 결과를 전달하는 유일한 방법은 callback이나 이벤트를 발생시키는 것이다. (다음을 봐라).


Argument Types

RCT_EXPORT_METHOD는 다음과 같은 모든 표준 JSON 객체 타입을 지원한다.

string (NSString)

number (NSInteger, float, double, CGFloat, NSNumber)

boolean (BOOL, NSNumber)

이 목록에 있는 모드 유형의 array (NSArray) 

string키와 이 목록의 모든 유형의 값 object (NSDictionary)

function (RCTResponseSenderBlock)

하지만 또한 RCTConvert 클래스에서 지원되는 모든 타입에서 동작한다(자세한 것은 RCTConvert를 봐라).

RCTConver 헬퍼 함수는 입력 JSON 값을 모두 받아들이고, native Objective-C 타입 또는 클래스에 매핑한다.


CalendarManager 예제에서, 우리는 native 메서드로 이벤트 일시를 전달해야 한다. bridge를 통해 자바스크립트 Date 객체를 보낼 수 없어서, date를 string 또는 number로 변환해야 한다. native 함수를  다음과 같이 작성할 수 있다.

또는 다음과 같이 작성할 수 있다.

하지만 자동 타입 변환 기능을 사용하면, 수동 변환 과정을 완전히 스킵하고 작성할 수 있다.

그러고 나서 다음 중 하나를 사용하여 자바스크립트에서 이 함수를 호출한다.

또는

그리고 두 값 모두 native NSDate로 잘 변환된다. Array 같이 잘못된 값을 사용할 경우, 도움이 되는 "RedBox" 에러 메시지를 생성한다.


CalendarManager.addEvent 메서드가 점점 더 복잡해지면서, 인수의 개수는 증가할 것이다. 어떤 인수들은 선택적일 수도 있다. 이런 경우에는 다음과 같이 이벤트 속성들의 dictionary를 받도록 API를 조금 변경하는 것을 고려해볼 만하다.

그리고 자바스크립트에서 호출한다.


NOTE: array와 map에 관해

Objective-C는 array와 map에 값 타입에 대해서는 어떠한 보장도 하지 않는다.  native 모듈은 string의 배열이라고 기대하지만, JavaScript에서 메서드를 number와 string을 모두 포함한 배열을 사용하여 호출한다면, NSNumber와 NSString이 모두 포함된 NSArray를 얻을 것이다. array의 경우 메서드 선언에 사용할 수 있는 NSStringArray나 UIColorArray 같은 RCTConvert는 유형이 지정된 컬렉션을 몇 가지 제공한다. map의 경우, RCTConvert helper 메서드를 수동으로 호출하여 각각의 값 유형을 체크하는 것이 개발자의 책임이다.



Callbacks


WARNING

이 섹션은 아직 callback에 대한 모범사례가 확실하게 없기 때문에 다른 부분들보다 더 실험적이다. 대부분의 경우 함수 호출 결과를 자바스크립트에 제공하는 데 사용된다..

RCTResponseSenderBlcok응 오직 하나의 인수만을 허용한다 - 자바스크립트 콜백으로 전달하기 위한 파마메터의 배열. 이 경우는 Node의 규칙을 사용하여 첫 번째 파라미터를 error 객체를 만드는 데 사용하고(보통 error가 없을 때는 null) 나머지는 함수의 결과로 사용한다.

native 모듈은 callback을 한 번만 호출해야 한다. callback을 저장하고 나중에 호출하는 것은 괜찮다. 이 패턴은 delegates를 필요로 하는 iOS API를 래핑 할 때 자주 사용된다 - RCTAlertManager 예제를 봐라. callback이 호출되지 않았다면, 메모리 누수가 생긴다. onSuccess와 onFail callback이 모두 전달된다고 해도, 둘 중 하나만 호출해야 한다. 


error 같은 객체를 자바스크립트에 전달하기를 원한다면, RCTUtils.h에 RCTMakeErorr를 사용해라. 당장은 

Error 모양의 딕셔너리를 자바스크립트에 전달하지만, 미래에는 진짜 자바스크립트 Error 객체를 자동으로 생성할 것이다.



Promise

Native 모듈은 promise를 전부 사용할 수 있다. 특히 ES2016의 async/await 구문을 사용할 때 코드를 단순화할 수 있다. 연결된 native 메서드의 마지막 파라미터가 RCTPromiseResolveBlock과 RCTPromiseRejectBlock일 경우, JS 메서드가 JS Promise 객체를 반환하는 것과 같다.


위 코드를 callback 대신에 promise를 사용해서 다음과 같이 리팩터링 해보자.

이 메서드의 자바스크립트 대응 부분은 Promise를 반환한다. 비동기 함수에서 await 키워드를 사용하여 호출하고 결과를 기다릴 수 있다는 것을 의미한다.


Threding

native 모듈은 어떤 스레드에서 호출되었는지 가정하면 안 된다. React Native는 별도의 직렬 GCD 큐에서 native 모듈 메서드를 호출하지만, 이는 구현 세부사항이고 변경될 수 있다. - (dispatch_queue_t) methodQueue 메서드를 사용하면 native 모듈이 메서드를 실행할 큐를 지정할 수 있다. 예를 들어, 메인 스레드 전용 iOS API를 사용하기 위해 다음과 같이 스레드를 지정할 수 있다.

비슷하게, 완료되는데 오랜 시간이 걸리는 작업일 경우, native 모듈은 차단하지 말고 작업이 실행될 큐를 지정할 수 있다. 예를 들어, RCTAsyncLocalStorage 모듈은 자체 큐를 만들어 잠재적으로 느린 디스크 접근으로 React Native 큐가 차단되지 않게 한다.

지정된 methodQueue는 너의 모듈에 모든 메서드에 의해 공유된다. 메서드들 중에 하나만 오래 걸릴 경우(또는 어떤 이유로 다른 메소들과는 다른 큐에서 실행될 필요가 있을 경우), 그 메서드 내부에 dispatch_async를 사용하여 다른 메서드에 영향 없이 특정 메서드를 다른 큐에서 실행할 수 있다.

NOTE: 모듈 간에 디스패치 큐 공유

methodQueue 메서드는 모듈이 초기화되고 bridge에 의해 유지될 때 한 번 호출되므로, 모듈 안에서 사용하지 않는다면 직접 큐를 유지할 필요가 없다. 하지만, 여러 모듈 간 같은 큐를 공유하고 싶다면 각각의 모듈이 같은 큐 인스턴스를 유지하고 반환해야 한다. 단지 각각의 모듈이 같은 이름의 큐를 반환하는 것으로는 동작하지 않는다.


Dependency Injection

bridge는 등록된 모든 RCTBridgeModuels을 자동으로 초기화하지만, 너의 모듈 인스턴스 자체를 인스턴스화할 수 있다(예를 들어 의존성 주입 같은).


RCTBridgeDelegate Protocol을 구현한 클래스를 생성하고, delegate를 인수로 사용하여 RCTBridge 초기화하고, 초기화된 bridge를 사용해서 RCTRootView를 초기화할 수 있다.


Exporting Constants

native 모듈은 런타임에 자바스크립트에서 즉시 사용 가능한 상수를 export 할 수 있다. 이 방법은 정적인 데이터로 통신할 때 유용하다. 이런 방법이 없다면 bridge를 통해 왕복해야 한다.

자바스크립트는 이 값이 동기화되어 즉시 사용할 수 있다.

상수는 오직 초기화 시에 export 되기 때문에, 런타임에 constantsToExport 값을 변경한다면 자바스크립트 환경에 영향을 주지 않는다.


Enum Constants

NS_ENUM을 통해 정의된 Enum은 RCTConvert 확장 없이는 메서드 인수로써 사용될 수 없다.


NS_ENUM 정의를 export 하려면 다음과 같이 해라.

다음과 같이 RCTConvert의 클래스 확장을 생성해야 한다.

그다음 메서드를 정의하고 enum 상수들을 다음과 같이 export 할 수 있다.

그다음 enum은 export 된 메서드에 전달되기 전에 제공된 selector(위 예제에서는 integerValue)를 사용해서 자동으로 언래핑 된다.


Sending Events to JavaScript

native 모듈은 직접 호출 없이 자바스크립트에 이벤트를 알릴 수 있다. RCTEventEmitter를 서브클래스로 사용해서, supportedEvents를 구현하고 self sendEventWithName을 호출하는 방법이 선호된다.

자바스크립트 코드는 모듈에서 새로운 NativeEventEmitter 인스턴스를 생성하여 이벤트들을 구독할 수 있다.

자바스크립트에 이벤트를 보내는 예제들은 RCTLocationObserver를 봐라.


Optimizing for zero listeners

리스너가 없는 상태에서 이벤트를 보내 불필요하게 리소스를 낭비하면 경고를 받을 수 있다. 이것을 피하고, 모듈의 작업량(예를 들어, 업스트림 알림을 구독 취소하거나 백그라운드 작업의 중지)을 최적화하기 위해, RCTEventEmitter 서브클래스에서 startObserving과 stopObserving을 재작성할 수 있다.


Exporting Swift

Swift는 매크로를 지원하지 않기 때문에 React Native에서 사용하려면 약간의 설정이 필요하지만 상대적으로 똑같이 동작한다.


같은 CalendarManager지만 Swift 클래스가 있다고 가정하자.

NOTE: Objective-C 런타임에 정확하게 클래스와 함수를 제대로 export 하기 위해 @objc 수정자를 사용하는 것이 중요하다.


그다음 React Native bridge에 필요한 정보를 등록할 개인 구현 파일을 생성한다.

Swift와 Objective-C가 익숙하지 않은 사람들을 위해서, iOS 프로젝트에서 두 가지 언어를 혼합해서 사용할 때, Swift에 Objective-C 파일을 보내기 위해 bridging 헤더로 알려진 추가적인 bridging 파일이 필요하다. Xcode File> New File 메뉴 옵션을 통해서 앱에 Swift파일을 추가해서 이 헤더 파일을 생성할 수 있다. 헤더 파일에 RCTBridgeModule.h를 import 해야 한다.

또 RCT_EXTERN_REMAP_MODULE과 _RCT_EXTERN_REMAP_METHOD를 사용해서 export 할 모듈이나 메서드의 자바스크립트 이름을 바꿀 수 있다. 더 자세히 알고 싶으면 RCTBridgeModuled을 봐라.


서드파티 모듈을 만들 때 중요한 점: Swift로 만든 정적인 라이브러리는 오직 Xcode 9 버전과 이후 버전에서만 지원된다. 모듈에 포함된 iOS 정적 라이브러리에서 Swift를 사용할 때는 Xcode 프로젝트에서 빌드하려면 너의 메인 앱 프로젝트에 Swift 코드와 bridging 헤더가 포함되어야 한다. 앱 프로젝트에 Swift 코드가 포함되지 않은 경우, 빈 .swift 파일과 빈 birding 헤더를 만들어 해결할 수 있다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari