참고
이 글을 읽고 이해한 내용을 그대로 작성한 글 입니다.
리액트네이티브에서 JS단과 Native단은 브릿지 기반의 통신을 합니다.
그러나 이러한 통신 방법은 이상적이지 않습니다.
브릿지는 메세지를 배치(일괄 처리)합니다.
그로인해 메시지를 보낼 때 지연이 발생할 수 있습니다.
또한 메시지를 JSON 형식으로 감싸기 위해 직렬화작업이 필요하고 이 또한 지연을 발생시킵니다.
Hermes, V8같은 Javascript 런타임들은 C와 C++로 작성되었습니다. 이는 고성능 작업을 위해서 입니다.
개발자들은 이러한 고성능 특성을 활용하기 위해 JSI라는 C++ API를 개발하였습니다.
JSI를 사용하여 얻을 수 있는 장점은 다음과 같습니다.
문자열과 숫자형변수의 정의는 아래처럼 간단하게 이루어집니다.
const number = 42
jsi::Value number = jsi::Value(42);
const name = "Marc"
jsi::Value name = jsi::String::createFromUtf8(runtime, "Marc")
JavaScript에서는 일반적인 방식으로 함수를 정의합니다:
const add = (first, second) => {
return first + second
}
JSI(C++)에서는 createFromHostFunction 메소드를 사용합니다:
auto add = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "add"),
2,
[](jsi::Runtime& runtime, const jsi::Value& thisValue,
const jsi::Value* arguments, size_t count) -> jsi::Value {
double result = arguments[0].asNumber() + arguments[1].asNumber();
return jsi::Value(result);
}
);
이렇게 정의된 함수는 JavaScript에서 직접 사용할 수 있습니다.
const result = add(5, 8)
C++ 측에서도 사용 가능합니다:
auto result = add.call(runtime, 5, 8);
global.add = add;
runtime.global().setProperty(runtime, "add", std::move(add));
JSI 함수의 특징:
기존 방식: 네이티브 코드를 호출할 때, JavaScript에서는 보통 비동기 처리를 해야 했습니다.
const result = await someNativeFunction();
JSI 방식: 함수 호출이 즉시 실행되고 결과를 바로 받습니다.
const result = someJSIFunction();
차이점: 코드가 순차적으로 실행되며, 결과를 기다리기 위해 특별한 처리(await)가 필요 없습니다.
기존 방식: JavaScript에서 네이티브 함수를 호출할 때, 여러 단계의 변환 과정을 거쳤습니다.
(JavaScript → JSON → 네이티브 코드 → JSON → JavaScript)
JSI 방식: JavaScript에서 직접 C++ 함수를 호출합니다. 중간 변환 과정이 없습니다.
차이점: 마치 JavaScript 내의 일반 함수를 호출하는 것처럼 빠르고 직접적입니다.
비동기 처리 불필요:
기존 방식: 네이티브 코드 호출은 항상 비동기로 처리되어, 콜백이나 Promise를 사용해야 했습니다.
예: someNativeFunction().then(result => { / 처리 / });
JSI 방식: 동기적으로 실행되므로, 일반 JavaScript 함수처럼 사용할 수 있습니다.
예: const result = someJSIFunction(); / 바로 다음 줄에서 결과 사용 가능 /
차이점: 코드 흐름이 더 자연스럽고, 콜백 지옥이나 복잡한 Promise 체인을 피할 수 있습니다.
JSI를 사용하면, C++과 JavaScript 사이의 직접적이고 효율적인 통신이 가능해집니다. 타입 변환이나 직렬화 과정 없이 데이터를 주고받을 수 있어 성능이 향상되며, 동기적 실행으로 코드의 가독성과 사용성도 개선됩니다.
RN에서 JSI를 이용해 C++코드에서 Javascript의 글로벌 비동기 함수에 접근할 수 있다.
Javascript에서는 Promise와 같은 전역 함수가 있고 이 함수는 비동기 작업을 처리하기 위해 많이 사용됩니다.
예를 들어, 데이터를 요청하고 응답을 기다린 다음 그 결과를 처리할 때 Promise를 사용합니다.
JSI를 사용하면 C++코드에서도 이 Promise 함수를 직접 가져와 사용할 수 있습니다.
이를 위해 다음과 같은 코드를 작성합니다.
auto promiseCtor = runtime.global().getPropertyAsFunction(runtime, "Promise");
auto promise = promiseCtor.callAsConstructor(runtime, resolve, reject);
C++은 잘 모르지만 GPT에게 물어본 위 코드의 설명은 아래와 같습니다.
이러한 과정 덕분에 C++ 코드에서 Javascript의 Promise를 직접 생성하고 사용할 수 있게 됩니다.
만약 함수가 const x = () => ... 처럼 이름이 없는 익명 함수라면, 이를 C++코드로 전달해서 호출 할 수 있습니다. 이를 위해 다음과 같은 코드를 작성합니다.
auto nativeFunc = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAsci(runtime, "someFunc"),
1, // 함수가 받을 매개변수의 개수
[](jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
auto func = arguments[0].asObject().asFunction(); // 전달된 첫 번째 매개변수를 함수로 변환
return func.call(runtime, jsi::Value(42)); // 이 함수를 호출하고 42라는 값을 전달
});
역시나 C++은 하나도 몰라서 GPT에게 물어본 결과 아래와같은 설명을 얻을 수 있었습니다.
이 코드를 통해, C++에서 익명 함수나 전달된 Javascript 함수를 받아서 처리할 수 있습니다.
JSI를 사용하면 C++코드에서 Javascript의 전역 비동기 함수(Promise)나 익명 함수에 접근하고 이를 호출할 수 있습니다.
즉, JSI를 사용하면 이러한 함수들에 더 빠르게 접근하고, Javascript와 네이티브 코드 간의 상호작용이 더 효율적이고 유연해집니다.
JSI는 Javascript 코드를 읽고 C++로 미리 함수를 만들거나 하는게 아닙니다.
JSI는 Javascript와 네이티브 코드(C++등)간의 상호작용을 더 효율적으로 만들어주는 인터페이스 입니다.
그러므로 JSI는 Javascript와 네이티브 코드가 서로 더 가깝게 통신할 수 있는 방법을 제공합니다.
const x = () => { console.log('Hello!'); };
이 코드는 여전히 Javascript 코드로 실행됩니다.
JSI는 이 코드를 C++로 변환하거나 미리 생성하지 않습니다. 대신, JSI를 통해 네이티브 코드(C++)에서 이 함수를 호출하거나, 반대로 Javascript에서 네이티브 코드에 접근할 수 있는 통로를 제공합니다.
JSI가 중요한 이유는 기존의 JS Bridge와 다르게 더 빠르고 효율적인 상호작용을 제공하기 때문입니다. 기존의 JS Bridge에서는 데이터를 JSON으로 직렬화하고, 이를 다시 역 직렬화 하는 과정이 있었기 때문에 성능에 부담이 있었습니다. JSI는 이러한 과정을 제거하고 Javascript와 네이티브 코드 간의 데이터 전달을 직접적으로 처리합니다.
JSI를 통해 Javascript 코드가 네이티브 코드(C++)와 더 긴밀하게 연결될 수 있습니다. 예를들어, 내가 만든 Javascript 함수가 C++ 코드에서 호출될 수 있습니다. 또는 C++에서 작성한 함수를 Javascript에서 사용할 수 있습니다.
하지만 이 모든 것은 필요할 때만 일어납니다. Javascript 함수가 C++로 미리 만들어져 있는 것이 아니라, JSI가 이 두 세계(Javascript와 네이티브)를 더 쉽게 연결해줄 뿐입니다.
이로 인해 RN에서 JSI를 사용하면 더 빠르고 유연한 상호작용이 가능해집니다.