Native Module

영차영차·2023년 1월 14일
0

ReactNative

목록 보기
3/5
post-thumbnail

자바스크립트와 네이티브는 어떻게 통신을 하게 되는 걸까?

그 전에 ReactNative에서 사용하는 Thread에 대해서 알아보자.

Thread란?

실행되는 프로세스 내에서 실제로 작업을 실행하는 주체이고, 명령어를 실행하여 처리하는 주체이다. 어떤 명령어를 넘겼을 때 이명령어를 하나하나씩 처리하는 것을 Thread라고 한다.

Thread가 ReactNative에는 4가지 종류가 있다.

  1. Main Thread or UI Thread : Native 영역에 레이아웃을 그려주는 역할
  2. JavaScript Thread : 작성한 JavaScript가 실행되는 곳
  3. Native Module Thread : Native Module을 다룰 때 사용하게 되는 Thread 이다.
  4. Shadow Thread : virtual DOM으로부터 새로운 Layout으로 변환하도록 계산해주는 역할을 한다.

ReactNative 앱이 최초로 실행되고, 첫 랜더를 하는 과정은 어떻게 될까?
사용자가 앱아이콘을 누르면 Activity 또는 AppDelegate가 실행되게 되면, Main Thread에서 자바스크립트를 로딩을 하게되어있다. 이 로딩하는 작업이 완료가되면 Main Thread는 이 자바스크립트 코드들을 JavaScript Thread로 보내게 된다. 보내지게 되면 JavaScript Thread에서 Diffing이라는 작업을 시작하게 된다. Diffing이란, virtual Dom과 실제 Dom element를 비교하며 변경되었는지 체크를 하고 새로운 virtual Dom을 만들어 내는 과정을 말한다. 이 과정이 끝나면 Shadow Thread로 새로운 virtual Dom을 넘기게 되고, 전달받은 virtual Dom을 실제 네이티브에서 Layout을 계산하는 과정을 거치게된다. 이 과정을 모두 마치면 Layout을 랜더링 해달라고 Main Thread로 보내게된다. 최초 랜더 1회에 관한 행동이 모두 끝나게 된다.

React-Native Bridge

Javascript와 Native가 서로 소통 할 수 있도록 돕는 역할.
예를들어 핸드폰의 배터리가 몇%인지 알고자 네이티브에 요청하고자 한다면, 이 Bridge를 통해서 Native영역으로 전달하고 난 뒤에 결과값을 다시 Bridge를 통해서 결과값을 받는다.

Bridge의 형태로 들어가게 되려면 변수들을 JSON의 형태로 변경해줘야하는데, 이 과정에서 리소스의 많은 비용이 든다.

Native Module ?

Native Module이란 Native API를 사용하기 위한 것. 주로 현재 위치, wifi 상태 등 native 영역에서만 알고 있는 정보에 접근하는것 또는 image processing 처럼 연산이 native의 높은 performance가 필요할때 사용한다.

Native Module과 JavaScript는 어떻게 동작할까?
JavaScript에서 Native Module을 호출하기 위해서 데이터를 요청하게 된다면 Bridge에 요청하기전에 JSON으로 변환하게 된다. 변환 이후에는 Native에 결과값을 요청하게 되는데 이때는 JSON의 형태 그대로 전달되게 되어있다. JSON의 형태를 받은뒤 Native Module에서 해당값을 parsing하게 되는데 parsing을 한뒤에 해당하는 결과값을 먼저 계산하고 난뒤에 Bridge로 다시 전달하게 된다. 이때 전달되기전에도 JSON의 형태로 변환을 하고 보내게 되어있다. 이렇게 전달받은 결과값은 JSON으로 parsing되었다 JavaScript에서 사용할수 있도록 변환이되어 전달이 되게 된다.

ReactNative에서 새로운 Architecture를 도입하고자 테스트를 하고 있는중인데, Bridge가 가지고 있는 본질적인 문제를 해결 하기 위해서이다.

Bridge가 가진 제한점은 무엇일까?

  1. asyncronous (비동기 처리) : 동기의 경우 어떤 작업이 끝날때까지 기다리는 것을 의미하고, 다음 프로세스를 기다리지 않고 다른 작업을 바로 실행하는 것을 말한다. 작업이 끝났는지 보장되지 않아, 끝났을때에 별도 처리가 필요하다. 비동기를 처리하기 위해서는 Promise를 선언해주거나 async/await 함수를 달아주어야 한다. JavaScript에서 Native Module을 호출하는 경우에 Native Module에서 응답이 올때까지 비동기로 처리를 해줘야했는데, 비동기로 처리한다는 이유는 JavaScript에서 Promise에서 resolve가 될때까지 기다려야한다.

  2. single threaded (싱글 스레드) : Javascript가 싱글 스레드에서 동작하기 때문에 Bridge도 싱글 스레드로 동작한다.

  3. extra overheads (변환시 드는 과도한 비용) : Bridge로 이동하게 될 때 JSON Object 변환하는 비용이 크다.

그렇다면 New Architecture는 무엇일까?

기존의 Bridge 대신 JSI가 해당 역할을 대신 하도록 수정하였다.
JSI란 Javascript Interface의 약자로, C++객체에 대한 참조를 할 수 있게 해주는 역할을 한다.

New Architecture의 장점은 무엇일까?

  1. 동기 실행이 가능하다는 점이다. C++모듈로 직접 접근 할 수 있다보니 비동기 통신이 아니어도 된다.

  2. 동시성. Javascript에서 다른 스레드에 있는 함수를 호출 할 수 있게 되었다.

  3. Overhead가 줄어듦. JSON Object로의 변환을 하지않고 C++언어로 통신하기 때문이다.

  4. iOS, Android간 내부 네이티브 모듈 코드 공유 가능. C++이 추가됨으로 플랫폼이 다르더라도 한개의 코드로 관리가 가능해진다.

  5. 타입의 안정성. 자동으로 생성 되는 코드 레이어에 의해서 자동으로 타입을 생성 하도록 되어있다.

New Architecture의 구성은?

1 Fabric

Favric은 랜더링 시스템인데, 이전 Architecture에서의 UI Manager가 담당하던 부분이라고 생각하면 된다. Shadow Thread에서 새로운 Shadow Tree를 계산하던 로직을 C++로직으로 변환 가능하도록 수정하였고, onLayout, onMeasure등 View의 위치, 사이즈등을 계산하던 로직을 비동기에서 동기 함수로 변환 했기에 많은 퍼포먼스 이득이 있다.

2 Turbo NativeModules

기존 Architecture에서는 NativeModule로 사용되던 것을 대신한다. Bridge가 사라지게 되면서 추가되었다. Turbo NativeModules의 장점으로는 Platform 전반적으로 Typecheck가 잘된다는 점이다. 플랫폼 별 코드 공유가 쉬움 (C++로 작성된 코드를 공유). 또한, 더 빠른 앱실행을 위해서 Native Module Lazy Loading이 적용되어있다. Lazy Loading은 최초에 모든 리소스를 로드 하는것이 아닌, 필요 할때 로드하는 방식으로 최초 로드시 부하를 줄이기 위함이다. 또한, JSI 사용으로 인하여 Native와 Javascript코드간 통신이 효율적이다. JSON Object로 변환 없이 C++코드만으로 통신이 가능하다.

3 CodeGen

3rd-party library에서 제공되는 코드를 인터페이스에 맞게 작성하면 JSI 관련 코드들을 만들어 주는것이다. 프로젝트를 빌드 할때 자동으로 실행된다. 빌드 시간에 영향을 준다.

Hermes

에르메스는 페이스북에서 만든 자바스크립트 엔진이다. 바이트코드 형태로 미리 컴파일 하여 저장한 뒤 사용한다. 에르메스는 컴파일 시간을 최소화하는데 효과적이게 설계되어 있다. 에르메스는 앱 최초 로딩시 jsbundle 파일(자바스크립트 로직을 하나로 뭉쳐논 것)을 읽어와 동작 가능한 javascript로 compile하게 된다. 이 과정이 느리면 4초까지 걸리는 것으로 파악된다.
에르메스는 최초 구동될때 시간을 최대한 줄이기 위해서 install 시점의 이전에 관련된 로직을 모두 컴파일해서 바이트코드 형태로 저장하는 것을 말한다. 이렇게 저장된 바이트코드를 읽어오게 되면 최초에 실행되는 시간이 절반가까이 줄어든다. 바이트코드로 읽어오는 장점으로는 사용하는 메모리가 감소하고 AAB/APK크기가 감소한다는 장점도 있다.

에르메스가 적용되었는지 확인하려면

const isHermes = () => global.HermesInternal !== null;

반드시 필요한 경우가 아니라면 Native Module을 만들기 보다 npm에 있는 package를 찾아서 쓰는 경우가 오히려 더 많은 것 같다. Native Module은 요구사항에 맞춰서 제작할 때 사용한다.

profile
FE Developer

0개의 댓글