본문으로 건너뛰기

Asynchronous Method To Synchronous Method in RN

· 약 13분
Dongmin Yu

리액트 네이티브 라이브러리 시 동기식으로 메소드를 호출하는 방법

iOS 네이티브 모듈에서 JavaScript로 결과를 전달하려면 콜백을 사용하거나 이벤트를 내보낼 수 있습니다. 그러나 동기식 네이티브 메서드를 만들고 싶다면 RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD 를 사용할 수 있습니다. 이 방법으로 iOS에서 기본 모듈의 기본 scaffolding 설정을 할 수 있습니다 .

References

(1) [React Native] Native Module (iOS)(번역) (2) React Native: How to export a method with a return value? (3) Is there way to call RCT_EXPORT_METHOD inside native iOS code

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD 를 사용하여 동기식 네이티브 메서드를 만들 수 있습니다. 이 메서드의 반환 유형은 개체 유형 (ID) 이어야 하며 JSON에서 직렬화 할 수 있어야 합니다. 이는 hook이 0 또는 JSON 값만 반환할 수 있음을 의미합니다. 그러나 대부분의 경우 비동기적으로 호출되는 RCT_EXPORT_METHOD를 사용하는 것이 좋습니다. 동기적으로 메서드를 호출하면 성능에 큰 영향을 줄 수 있기 때문입니다.

References

(1) [React Native] Native Module (iOS)(번역) (2) Synchronous returns in React Native native modules | by Alec Larson | Medium (3) react-native/RCTBridgeModule.h at main · facebook/react-native

비동기 메소드를 줄이기 위한 접근 방법

Q. 모든 메소드에 동기 응답을 받고 싶은 것은 아니고, Segway BLE Module과 같은 외부 Pod이 응답가능한 상태인지 정도를 확인하려는 용도야. 이런 경우엔 동기적으로 응답을 받는 것보다 아무것도 응답받지 않고, 네이티브 코드가 자신의 상태를 체크해 디바이스로 이벤트를 보내는게 더 바람직한 방법인지?

A. 네이티브 코드가 자신의 상태를 체크하고 디바이스로 이벤트를 보내는 것이 더 바람직한 방법일 수 있습니다. 이렇게 하면 네이티브 코드가 비동기적으로 실행되므로 성능에 영향을 미치지 않습니다.

Q. 이벤트에미터를 이용해 이벤트를 보내려 할 때, 이벤트 이름을 여러가지로 하면 리액트네이티브 브릿지 영역에서 그것을 이름과 상관없이 모두 구독하는 방법은 없고 일회성이라도 따로 리스너를 만들어 해당 이름의 이벤트를 리슨하다가 트리거된 뒤에 삭제하는 방법이 최선의 방법인지?

A. 각 이벤트에 대해 따로 리스너를 만들어 해당 이름의 이벤트를 리슨하다가 트리거된 뒤에 삭제하는 방법이 일반적입니다.

Q. 그렇다면 각각 다른 메소드들이 이벤트 이름을 하나로 통일하고, 특정한 형식에 따라 무엇에 대한 정보를 담은 데이터인지, 실제 데이터를 담은 코드와 함께 리턴하는 방식으로 구현하는 것은 괜찮은 아이디어일까

A. 이 방법을 사용하면 이벤트 리스너를 하나만 만들어서 여러 가지 이벤트를 처리할 수 있습니다.

Q. 모바일 애플리케이션에서 동기 메소드를 사용하면 다른 코드들의 동작을 Blocking해서 성능 저하를 일으킬 수 있다는 것은 이해했어. 메소드 내의 복잡한 로직을 실행하기 전에 간단한 boolean이나 숫자, 문자열 같은 것을 상태체크를 위해 먼저 리턴하고, 이후 요청을 전달받은 다른 함수가 그에 대한 응답을 이벤트에밋터를 통해 보내는 것 역시 권장되지 않거나 비효율적인 방법이야?

A. 메소드 내의 복잡한 로직을 실행하기 전에 간단한 boolean이나 숫자, 문자열 같은 것을 상태체크를 위해 먼저 리턴하고 이후 요청을 전달받은 다른 함수가 그에 대한 응답을 이벤트 에미터를 통해 보내는 것은 가능합니다. 이 방법을 사용하면 메소드가 비동기적으로 실행되므로 성능에 영향을 미치지 않습니다.

다음은 blocking method가 값을 리턴하면서 다른 메소드에 이벤트를 전달해 비동기적으로 sendEvent하는 예제 코드입니다.

import { NativeModules, NativeEventEmitter } from "react-native";
const MyNativeModule = NativeModules.MyNativeModule;
const myEventEmitter = new NativeEventEmitter(MyNativeModule);
myEventEmitter.addListener("MyEvent", (event) => {
  console.log(event); // event data
});
const result = MyNativeModule.myBlockingMethod();
console.log(result); // blocking method return value

이 예제에서 MyNativeModule.myBlockingMethod는 blocking method로 값을 리턴합니다. 이 메소드는 또한 다른 메소드에 이벤트를 전달하여 비동기적으로 sendEvent를 호출합니다. myEventEmitter.addListener를 사용하여 MyEvent 이벤트를 수신하고 처리합니다.

Objective-C++ 를 사용해 blocking method와 sendEvent를 구현한 iOS 네이티브 측의 코드도 코드

// MyNativeModule.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface MyNativeModule : RCTEventEmitter <RCTBridgeModule>
@end
// MyNativeModule.m
#import "MyNativeModule.h"
@implementation MyNativeModule
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
      return @[@"MyEvent"];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(myBlockingMethod) {
      // Perform blocking operation and return result
  NSString *result = @"Blocking method result";
 
  // Send event asynchronously
  [self sendEventWithName:@"MyEvent" body:@{@"key": @"value"}];
 
  return result;
}
@end

이 예제에서 myBlockingMethod는 blocking method로 값을 리턴합니다. 이 메소드는 또한 sendEventWithName 메소드를 호출하여 비동기적으로 MyEvent 이벤트를 전달합니다. React Native 측에서는 myEventEmitter.addListener를 사용하여 이 이벤트를 수신하고 처리할 수 있습니다.

이 방식을 사용할 때 주의해야 할 점

  1. 이벤트 리스너를 적절한 시점에 추가하고 제거해야 합니다. 이벤트 리스너가 더 이상 필요하지 않을 때 제거하지 않으면 메모리 누수가 발생할 수 있습니다.
  2. blocking method는 메인 스레드에서 실행되므로 너무 오래 걸리는 작업을 수행하면 애플리케이션의 반응성이 저하될 수 있습니다. 가능한 한 빠르게 완료되는 작업만 blocking method에서 수행하는 것이 좋습니다.
  3. 이벤트 데이터의 형식을 명확하게 정의해야 합니다. 이벤트 데이터의 형식이 명확하지 않으면 React Native 측에서 이벤트를 처리하는 데 어려움이 발생할 수 있습니다.
  4. 네이티브 모듈과 React Native 코드 간의 인터페이스를 명확하게 정의해야 합니다. 인터페이스가 명확하지 않으면 코드 유지보수가 어려워질 수 있습니다. 리액트네이티브의 nativeeventemitter에 addListener(callback).remove() 이렇게 응답받았을때의 코드 바로 뒤에 remove 메소드를 붙이면 이벤트를 한번이라도 받은 뒤에 삭제돼 아니면 그냥 바로 삭제돼?

NativeEventEmitteraddListener 메소드는 이벤트 리스너를 추가하고 EmitterSubscription 객체를 반환합니다. 이 객체의 remove 메소드를 호출하면 이벤트 리스너가 제거됩니다.

const subscription = myEventEmitter.addListener("MyEvent", (event) => {
  console.log(event); // event data
});
// ...
subscription.remove(); // remove event listener

이 예제에서 subscription.remove()를 호출하면 이벤트 리스너가 제거됩니다. 따라서 addListener(callback).remove()와 같은 코드는 이벤트 리스너를 바로 제거하므로 이벤트를 한 번도 받지 못합니다.

이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하는 예제

let subscription;
const handleEvent = (event) => {
  console.log(event); // event data // Remove event listener
  subscription.remove();
};
subscription = myEventEmitter.addListener("MyEvent", handleEvent);

이 예제에서 handleEvent 함수는 이벤트 리스너의 콜백 함수로, 이벤트를 처리한 후 subscription.remove()를 호출하여 이벤트 리스너를 제거합니다. 따라서 이 방식을 사용하면 이벤트가 한 번 발생한 후에 이벤트 리스너가 제거됩니다.

이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하는 방식을 사용하는 경우와 그렇지 않은 경우의 차이점

  • 이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하는 방식을 사용하는 경우: 이벤트가 한 번 발생한 후에 이벤트 리스너가 제거됩니다. 따라서 이 방식은 이벤트를 한 번만 처리하고 싶을 때 유용합니다.
  • 이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하지 않는 경우: 이벤트 리스너가 계속 유지됩니다. 따라서 이 방식은 여러 번 발생하는 이벤트를 처리하고 싶을 때 유용합니다. 두 가지 방식 중 어느 것이 더 나은지는 애플리케이션의 요구사항과 구현 세부 사항에 따라 달라집니다. 애플리케이션의 요구사항에 맞는 방식을 선택하여 사용하는 것이 좋습니다.

NativeEventEmitter의 래퍼 훅으로 listenOnce 함수를 사용하여 일회성 이벤트 리스너를 등록하고 이벤트를 최초 한 번 받은 후에 스스로 할당을 해제하는 예제 코드

import { NativeEventEmitter } from "react-native";
const listenOnce = (eventEmitter, eventName, callback) => {
  let subscription;
  const handleEvent = (...args) => {
    callback(...args); // Remove event listener
    subscription.remove();
  };
  subscription = eventEmitter.addListener(eventName, handleEvent);
};
// Usage example

const myEventEmitter = new NativeEventEmitter(MyNativeModule);
listenOnce(myEventEmitter, "MyEvent", (event) => {
  console.log(event); // event data
});

이 예제에서 listenOnce 함수는 eventEmitter, eventName, callback 세 개의 인수를 받습니다. 이 함수는 일회성 이벤트 리스너를 등록하고 이벤트가 발생하면 콜백 함수를 호출한 후에 이벤트 리스너를 제거합니다. 따라서 이 방식을 사용하면 이벤트가 한 번 발생한 후에 자동으로 할당이 해제됩니다.