はじめに
React-Nativeを使ってiOSアプリを作ってみる(その1 環境構築)
React-Nativeを使ってiOSアプリを作ってみる(その2 画面遷移)
👆これの続きです。
Why a New Architecture
(React Native(公式)
経緯はよくわからんが公式サイトによると、
React-Native
のVer0.68
からNew Architecture
になったらしい。
元々のReact-NativeではBridge
というコンポーネントを使ってJS layer
とnative layer
間のデータのやり取りを非同期でシリアライズ/デシリアライズをしていて不必要に待たされるだとか余分なオーバヘッドが発生していた。。。
で、Ver0.68からNew Acrhitecture
でBridge
からJSI(JavaScript Interface)
を採用したことで、C++を呼べるようにして、
不必要に非同期で動いていた箇所を同期処理にしたり、シリアライズ,デシリアライズのオーバーヘッドを無くしたりしたそうです。
また、C++を導入したことで、プラットフォーム間に依存しない部分を抽象化して共有する狙いが有るみたいです。
ということで、公式サイトの手順に沿ってC++のNative Module
の作り方を書いてこうと思います。
手順は大まかに以下の4ステップです。
※ Androidの場合、ちょっとやり方が変わります。
- プロジェクトを作成する
- JavaScript用の定義作成
- cocoapods用にPodSpecファイル作成
- アプリで使用するために、モジュールを登録
- モジュールを作成
- アプリで作成したモジュールを実行する
1. まず手始めに、手順の確認とモジュールを追加するプロジェクトを作成する
npx react-native init CxxTurboModulesGuide
cd CxxTurboModulesGuide
# ios フォルダで以下を実行(作りながら何回かやるし、今やらんでもいいかも)
cd ./ios
#
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
cd ../
# C++などのファイルの格納先
# 内部的にパスが合ってればいいので、プロジェクト直下じゃなくてもフォルダ名がtmじゃなくてもOK
mkdir tm
# フォルダ構成はこんな感じになっているはず。
tree ../CxxTurboModulesGuide -d -L 1
../CxxTurboModulesGuide
├── __tests__
├── android
├── ios
├── node_modules
├── tm
└── vendor
7 directories
2.JavaScript用の定義作成
tm フォルダで以下のファイルを作成する。
touch ./ios/NativeSampleModule.tx
内容は以下の感じで、ここで定義したのが作成したアプリのインターフェースになるみたい。
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
// import type {TurboModule} from 'react-native'; in future versions
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
3. cocoapods用にPodSpecファイル作成
3.1 package.json更新
package.jsonに以下を追加する。
jsSrcsDir
はパスが合ってればOK
{
// ...
"codegenConfig": {
"name": "AppSpecs",
"type": "all",
"jsSrcsDir": "tm",
"android": {
"javaPackageName": "com.facebook.fbreact.specs"
}
}
}
3.2 AppTurboModules.podspecファイルを作成
tmフォルダにAppTurboModules.podspecファイルを作成する。
touch ./tm/AppTurboModules.podspec
内容は以下、とりあえず適当に書いた。(動作確認済み)
ここにビルドに必要な情報を書くっぽい(CMakefileみたいな感じ?)
require "json"
Pod::Spec.new do |s|
s.name = "AppTurboModules"
s.version = "0.0.1"
s.summary = "description"
# descriptionはなくてもいいみたい。
#s.description = package["description"]
s.homepage = "http://localhost/"
s.license = "MIT"
s.platforms = { :ios => "12.4" }
s.author = "Anonymous"
s.source = { :git => "", :tag => "#" }
s.source_files = "**/*.{h,cpp}"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
install_modules_dependencies(s)
end
3.3 ios/Podfileを更新する
use_react_native()の直下に以下を追加
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
:hermes_enabled => flags[:hermes_enabled],
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
+ # パスが合ってればOK
+ pod 'AppTurboModules', :path => "./tm"
+ end
4. アプリで使用するために、モジュールを登録
ios/CxxTurboModulesGuide/AppDelegate.mm
に以下を追加する。
(Objective-Cの関数定義ってなんかクセが強いですね、、)
別にObjective-Cでクラスを返す関数を作らなくても、C++側で実装して、Objective-Cから呼び出すこともできるっぽい
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
+ #import <React/CoreModulesPlugins.h>
+ #import <ReactCommon/RCTTurboModuleManager.h>
+ #import <NativeSampleModule.h>
+ @interface AppDelegate () <RCTTurboModuleManagerDelegate> {}
+ @end
// ....
+ // ファイルの末尾あたりにでも追加しておけばOK
+ #pragma mark RCTTurboModuleManagerDelegate
+
+ // getTurboModule関数を追加
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(
+ const std::string &)name
+ jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
+ {
+ // NativeSampleModuleが今回追加するモジュールのクラス
+ // NativeSampleModule.tsからも使われるっぽいので、一貫性のある書き方をすること。
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
+ return nullptr;
+ }
ファイルを更新したら一旦、以下を実行。
CxxTurboModulesGuide/ios/build/generated/iosに
AppSpecsJSI.h
と AppSpecsJSI-generated.cpp
が作成される。
AppSpec
はプレフィックスでpackage.json
のcodegenConfig.name
の値らしい
cd ios
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
cd ../
# AppSpecsJSI.h` と `AppSpecsJSI-generated.cpp`ができているはず。
ls ./ios/build/generated/ios
5. モジュールを作成
hファイルとcppファイルを作成する
touch ./tm/NativeSampleModule.h
touch ./tm/NativeSampleModule.cpp
#pragma once
#include <React-Codegen/AppSpecsJSI.h>
#include <memory>
#include <string>
namespace facebook::react {
// NativeSampleModuleCxxSpecは AppSpecsJSI.hで定義されてるクラス
class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);
std::string reverseString(jsi::Runtime& rt, std::string input);
};
} // namespace facebook::react
#include "NativeSampleModule.h"
namespace facebook::react {
NativeSampleModule::NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleModuleCxxSpec(std::move(jsInvoker)) {}
// #1で定義したTypeScriptと同じ関数名にすること
std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) {
// 引数文字列を反転させて戻り値に返してるだけ
return std::string(input.rbegin(), input.rend());
}
} // namespace facebook::react
.cpp/.hファイルを追加したら以下を実行
cd ./ios/
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
cd ../
6. アプリで作成したモジュールを実行する
//...
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
// パスが合っていれば、どこでもいいはず。。
+ import NativeSampleModule from './tm/NativeSampleModule';
//...
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
+ <Section title="Cxx TurboModule">
+ NativeSampleModule.reverseString(...) ={' '}
+ {NativeSampleModule.reverseString(
+ 'the quick brown fox jumps over the lazy dog'
+ )}
+ </Section>
<Section title="Step One">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
screen and then come back to see your edits.
</Section>
//...
こんな感じで引数で渡した文字列が反転していればOKです。
最後に
個人的にはAndroidだからJavaとか,iOSだからSwift/Objective-cとか環境に合わせて使い分けるのめんどくさいし、ReactとC++でまとめてくれると嬉しいなと思いました。
追伸
C++ Turbo Native Modules
の最後の方で、
こんな一文👇を見つけた。
Calling OS specific APIs
You can still call OS specific functions in the compilation unit (e.g., NS/CF APIs on Apple or Win32/WinRT APIs on Windows) as long as the method signatures only use std:: or jsi:: types.For Apple specific APIs you need to change the extension of your implementation file from .cpp to .mm to be able to consume NS/CF APIs.
std::, jsi::をメソッドシグネチャに使っていれば、
OS固有のAPIが使えるっぽいけどmacOS
のAPIをiOS
でも使えるということで合ってる?
おしえて、えらい人!