メインコンテンツへスキップ

iOSネイティブモジュール

info

ネイティブモジュールとネイティブコンポーネントは、レガシーアーキテクチャで使用されている安定した技術です。これらは、新しいアーキテクチャが安定した将来に非推奨となる予定です。新しいアーキテクチャでは、同様の結果を得るためにTurbo Native ModuleFabric Native Componentを使用します。

iOS向けネイティブモジュールへようこそ。まず、ネイティブモジュールとは何かを紹介するネイティブモジュールのイントロダクションをお読みください。

カレンダーネイティブモジュールの作成

以下のガイドでは、JavaScriptからAppleのカレンダーAPIにアクセスできるようにするネイティブモジュールCalendarModuleを作成します。最終的には、JavaScriptからCalendarModule.createCalendarEvent('Dinner Party', 'My House');を呼び出し、カレンダーイベントを作成するネイティブメソッドを起動できるようになります。

セットアップ

まず、React Nativeアプリケーション内のiOSプロジェクトをXcodeで開きます。React Nativeアプリ内のiOSプロジェクトは以下の場所にあります。

Image of opening up an iOS project within a React Native app inside of xCode.
iOSプロジェクトの場所を示す画像

ネイティブコードの記述にはXcodeの使用を推奨します。XcodeはiOS開発向けに作られており、コードの構文エラーのような小さなエラーを迅速に解決するのに役立ちます。

カスタムネイティブモジュールファイルの作成

最初のステップは、主要なカスタムネイティブモジュールのヘッダーファイルと実装ファイルを作成することです。RCTCalendarModule.hという新しいファイルを作成します。

Image of creating a class called  RCTCalendarModule.h.
AppDelegateと同じフォルダ内にカスタムネイティブモジュールファイルを作成する画像

そして、以下の内容を追加してください。

objectivec
//  RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end

作成するネイティブモジュールに合った任意の名前を使用できます。カレンダーネイティブモジュールを作成しているので、クラス名をRCTCalendarModuleとします。ObjCにはJavaやC++のような言語レベルの名前空間のサポートがないため、慣習としてクラス名の前にサブストリングを付けます。これは、アプリケーション名やインフラ名の略称などが考えられます。この例のRCTはReactを指します。

以下でわかるように、CalendarModuleクラスはRCTBridgeModuleプロトコルを実装しています。ネイティブモジュールは、RCTBridgeModuleプロトコルを実装したObjective-Cクラスです。

次に、ネイティブモジュールの実装を始めましょう。同じフォルダに、Xcodeのcocoa touch classを使用して対応する実装ファイルRCTCalendarModule.mを作成し、以下の内容を含めます。

objectivec
// RCTCalendarModule.m
#import "RCTCalendarModule.h"

@implementation RCTCalendarModule

// To export a module named RCTCalendarModule
RCT_EXPORT_MODULE();

@end

モジュール名

今のところ、RCTCalendarModule.mネイティブモジュールにはRCT_EXPORT_MODULEマクロのみが含まれています。これはネイティブモジュールクラスをReact Nativeにエクスポートし、登録します。RCT_EXPORT_MODULEマクロは、JavaScriptコード内でモジュールがアクセス可能になる名前を指定するオプションの引数を取ることもできます。

この引数は文字列リテラルではありません。以下の例では、RCT_EXPORT_MODULE("CalendarModuleFoo")ではなくRCT_EXPORT_MODULE(CalendarModuleFoo)が渡されています。

objectivec
// To export a module named CalendarModuleFoo
RCT_EXPORT_MODULE(CalendarModuleFoo);

すると、このネイティブモジュールはJSで次のようにアクセスできます。

tsx
const {CalendarModuleFoo} = ReactNative.NativeModules;

名前を指定しない場合、JavaScriptモジュール名はObjective-Cのクラス名から "RCT" または "RK" のプレフィックスを取り除いたものと一致します。

以下の例に従い、引数なしでRCT_EXPORT_MODULEを呼び出してみましょう。その結果、モジュールはObjective-Cのクラス名からRCTを取り除いたCalendarModuleという名前でReact Nativeに公開されます。

objectivec
// Without passing in a name this will export the native module name as the Objective-C class name with “RCT” removed
RCT_EXPORT_MODULE();

すると、このネイティブモジュールはJSで次のようにアクセスできます。

tsx
const {CalendarModule} = ReactNative.NativeModules;

ネイティブメソッドをJavaScriptにエクスポートする

React Nativeは、明示的に指定されない限り、ネイティブモジュール内のメソッドをJavaScriptに公開しません。これはRCT_EXPORT_METHODマクロを使用して行います。RCT_EXPORT_METHODマクロで書かれたメソッドは非同期であり、戻り値の型は常にvoidです。RCT_EXPORT_METHODメソッドからJavaScriptに結果を渡すためには、コールバックを使用するか、イベントを発行します(後述)。それでは、RCT_EXPORT_METHODマクロを使用してCalendarModuleネイティブモジュール用のネイティブメソッドを設定しましょう。これをcreateCalendarEvent()と名付け、今のところはnameとlocationを文字列として引数に取ります。引数の型のオプションについては後ほど説明します。

objectivec
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}

メソッドがRCTの引数変換に依存している場合(下記の引数の型を参照)を除き、TurboModulesではRCT_EXPORT_METHODマクロは不要になることに注意してください。最終的に、React NativeはRCT_EXPORT_MACROを削除する予定なので、RCTConvertの使用は推奨しません。代わりに、メソッド本体内で引数変換を行うことができます。

createCalendarEvent()メソッドの機能を構築する前に、メソッドにコンソールログを追加して、React NativeアプリケーションのJavaScriptから呼び出されたことを確認できるようにしましょう。ReactのRCTLog APIを使用します。ファイルの先頭でそのヘッダーをインポートし、ログ呼び出しを追加します。

objectivec
#import <React/RCTLog.h>
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}

同期メソッド

同期的なネイティブメソッドを作成するには、RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHODを使用できます。

objectivec
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}

このメソッドの戻り値の型はオブジェクト型(id)でなければならず、JSONにシリアライズ可能であるべきです。これは、このフックがnilまたはJSON値(例: NSNumber, NSString, NSArray, NSDictionary)のみを返すことができることを意味します。

現時点では、同期メソッドの使用は推奨していません。メソッドを同期的に呼び出すと、パフォーマンスに大きなペナルティが生じ、ネイティブモジュールにスレッド関連のバグを引き起こす可能性があるためです。さらに、RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHODを使用する場合、アプリはGoogle Chromeデバッガーを使用できなくなります。これは、同期メソッドがJS VMとアプリでメモリを共有する必要があるためです。Google Chromeデバッガーの場合、React NativeはGoogle Chrome内のJS VMで実行され、WebSocketを介してモバイルデバイスと非同期に通信します。

作成したものをテストする

この時点で、iOSでのネイティブモジュールの基本的な骨組みができました。ネイティブモジュールにアクセスし、エクスポートされたメソッドをJavaScriptで呼び出してテストしてみましょう。

アプリケーション内でネイティブモジュールのcreateCalendarEvent()メソッドへの呼び出しを追加したい場所を見つけます。以下は、アプリに追加できるコンポーネントNewModuleButtonの例です。NewModuleButtononPress()関数内でネイティブモジュールを呼び出すことができます。

tsx
import React from 'react';
import {Button} from 'react-native';

const NewModuleButton = () => {
const onPress = () => {
console.log('We will invoke the native module here!');
};

return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};

export default NewModuleButton;

JavaScriptからネイティブモジュールにアクセスするには、まずReact NativeからNativeModulesをインポートする必要があります。

tsx
import {NativeModules} from 'react-native';

その後、NativeModulesからCalendarModuleネイティブモジュールにアクセスできます。

tsx
const {CalendarModule} = NativeModules;

これでCalendarModuleネイティブモジュールが利用可能になったので、ネイティブメソッドcreateCalendarEvent()を呼び出すことができます。以下では、NewModuleButtononPress()メソッドに追加しています。

tsx
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};

最後のステップは、React Nativeアプリを再ビルドして、最新のネイティブコード(新しいネイティブモジュールを含む!)を利用できるようにすることです。React Nativeアプリケーションがあるコマンドラインで、以下を実行します。

shell
npm run ios

イテレーション中のビルド

これらのガイドを進め、ネイティブモジュールを繰り返し開発する際には、JavaScriptから最新の変更にアクセスするためにアプリケーションのネイティブビルドを行う必要があります。これは、書いているコードがアプリケーションのネイティブ部分に存在するためです。React NativeのMetroバンドラーはJavaScriptの変更を監視し、JSバンドルを即座に再ビルドできますが、ネイティブコードに対してはそうではありません。したがって、最新のネイティブの変更をテストしたい場合は、上記のコマンドを使用して再ビルドする必要があります。

まとめ✨

これで、JavaScriptでネイティブモジュールのcreateCalendarEvent()メソッドを呼び出せるはずです。関数内でRCTLogを使用しているため、アプリでデバッグモードを有効にし、ChromeのJSコンソールまたはモバイルアプリデバッガーFlipperを見ることで、ネイティブメソッドが呼び出されていることを確認できます。ネイティブモジュールメソッドを呼び出すたびに、RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);のメッセージが表示されるはずです。

Image of logs.
FlipperでのiOSログの画像

この時点で、iOSネイティブモジュールを作成し、React NativeアプリケーションのJavaScriptからそのメソッドを呼び出しました。ネイティブモジュールメソッドが受け取る引数の型や、ネイティブモジュール内でコールバックやPromiseを設定する方法など、さらに詳しく学ぶために読み進めてください。

カレンダーネイティブモジュールを超えて

より良いネイティブモジュールのエクスポート

上記のようにNativeModulesからネイティブモジュールを取得してインポートするのは、少し扱いにくいです。

ネイティブモジュールの利用者が、アクセスするたびにそのようなことをする必要がないように、モジュールのJavaScriptラッパーを作成することができます。`NativeCalendarModule.js`という名前の新しいJavaScriptファイルを作成し、以下の内容を記述します。

tsx
/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:

* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;

このJavaScriptファイルは、JavaScript側の機能を追加するのにも良い場所になります。例えば、TypeScriptのような型システムを使用している場合、ここでネイティブモジュールの型アノテーションを追加できます。React NativeはまだネイティブからJSへの型安全性をサポートしていませんが、これらの型アノテーションにより、すべてのJSコードが型安全になります。これらのアノテーションは、将来的に型安全なネイティブモジュールに移行するのを容易にします。以下は、Calendar Moduleに型安全性を追加する例です。

tsx
/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:
*
* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
interface CalendarInterface {
createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;

他のJavaScriptファイルでは、このようにしてネイティブモジュールにアクセスし、そのメソッドを呼び出すことができます。

tsx
import NativeCalendarModule from './NativeCalendarModule';
NativeCalendarModule.createCalendarEvent('foo', 'bar');

これは、CalendarModuleをインポートする場所がNativeCalendarModule.jsと同じ階層にあることを前提としています。必要に応じて相対インポートを更新してください。

引数の型

ネイティブモジュールのメソッドがJavaScriptで呼び出されると、React Nativeは引数をJSオブジェクトから対応するObjective-C/Swiftオブジェクトに変換します。例えば、Objective-CのネイティブモジュールメソッドがNSNumberを受け入れる場合、JSでは数値を渡してメソッドを呼び出す必要があります。React Nativeが変換を処理します。以下は、ネイティブモジュールメソッドでサポートされている引数の型と、それらがマッピングされるJavaScriptの同等物のリストです。

Objective-CJavaScript
NSStringstring, ?string
BOOLboolean
doublenumber
NSNumber?number
NSArrayArray, ?Array
NSDictionaryObject, ?Object
RCTResponseSenderBlockFunction (success)
RCTResponseSenderBlock, RCTResponseErrorBlockFunction (failure)
RCTPromiseResolveBlock, RCTPromiseRejectBlockPromise

以下の型は現在サポートされていますが、TurboModulesではサポートされません。使用を避けてください。

  • Function (failure) -> RCTResponseErrorBlock
  • Number -> NSInteger
  • Number -> CGFloat
  • Number -> float

iOSの場合、RCTConvertクラスでサポートされている任意の引数型を持つネイティブモジュールメソッドを書くこともできます(サポートされている内容の詳細はRCTConvertを参照してください)。RCTConvertヘルパー関数はすべて、JSON値を入力として受け取り、それをネイティブのObjective-C型またはクラスにマッピングします。

定数のエクスポート

ネイティブモジュールは、ネイティブメソッドconstantsToExport()をオーバーライドすることで定数をエクスポートできます。以下ではconstantsToExport()がオーバーライドされ、JavaScriptで次のようにアクセスできるデフォルトのイベント名プロパティを含むDictionaryを返します。

objectivec
- (NSDictionary *)constantsToExport
{
return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
}

この定数は、JSでネイティブモジュールのgetConstants()を呼び出すことでアクセスできます。

tsx
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);

技術的には、constantsToExport()でエクスポートされた定数にNativeModuleオブジェクトから直接アクセスすることが可能です。これはTurboModulesではサポートされなくなるため、将来の移行を避けるために上記のアプローチに切り替えることをコミュニティに推奨します。

定数は初期化時にのみエクスポートされるため、実行時にconstantsToExport()の値を変更してもJavaScript環境には影響しませんので注意してください。

iOSの場合、constantsToExport()をオーバーライドするなら、+ requiresMainQueueSetupも実装して、モジュールがJavaScriptコードの実行前にメインスレッドで初期化する必要があるかどうかをReact Nativeに知らせるべきです。そうしないと、+ requiresMainQueueSetup:で明示的にオプトアウトしない限り、将来モジュールがバックグラウンドスレッドで初期化される可能性があるという警告が表示されます。モジュールがUIKitへのアクセスを必要としない場合は、+ requiresMainQueueSetupにNOで応答するべきです。

コールバック

ネイティブモジュールは、コールバックというユニークな種類の引数もサポートしています。コールバックは、非同期メソッドのためにObjective-CからJavaScriptへデータを渡すために使用されます。また、ネイティブ側から非同期にJSを実行するためにも使用できます。

iOSでは、コールバックはRCTResponseSenderBlock型を使用して実装されます。以下では、createCalendarEventMethod()にコールバックパラメータmyCallBackが追加されています。

objectivec
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
location:(NSString *)location
myCallback:(RCTResponseSenderBlock)callback)

その後、ネイティブ関数内でコールバックを呼び出し、JavaScriptに渡したい結果を配列で提供できます。RCTResponseSenderBlockは、JavaScriptコールバックに渡すパラメータの配列という1つの引数のみを受け入れることに注意してください。以下では、以前の呼び出しで作成されたイベントのIDを返します。

コールバックはネイティブ関数が完了した直後に呼び出されるわけではないことを強調しておくことが重要です。通信は非同期であることを覚えておいてください。

objectivec
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
NSInteger eventId = ...
callback(@[@(eventId)]);

RCTLogInfo(@"Pretending to create an event %@ at %@", title, location);
}

このメソッドは、JavaScriptで以下のようにアクセスできます。

tsx
const onSubmit = () => {
CalendarModule.createCalendarEvent(
'Party',
'04-12-2020',
eventId => {
console.log(`Created a new event with id ${eventId}`);
},
);
};

ネイティブモジュールは、コールバックを一度だけ呼び出すことになっています。しかし、コールバックを保存しておき、後で呼び出すことも可能です。このパターンは、デリゲートを必要とするiOS APIをラップするためによく使用されます。例としてRCTAlertManagerを参照してください。コールバックが一度も呼び出されない場合、メモリリークが発生します。

コールバックでのエラーハンドリングには2つのアプローチがあります。1つ目はNodeの慣習に従い、コールバック配列に渡される最初の引数をエラーオブジェクトとして扱うことです。

objectivec
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
NSNumber *eventId = [NSNumber numberWithInt:123];
callback(@[[NSNull null], eventId]);
}

JavaScriptでは、最初の引数をチェックしてエラーが渡されたかどうかを確認できます。

tsx
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};

もう一つの選択肢は、onFailureとonSuccessという2つの別々のコールバックを使用することです。

objectivec
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title
location:(NSString *)location
errorCallback: (RCTResponseSenderBlock)errorCallback
successCallback: (RCTResponseSenderBlock)successCallback)
{
@try {
NSNumber *eventId = [NSNumber numberWithInt:123];
successCallback(@[eventId]);
}

@catch ( NSException *e ) {
errorCallback(@[e]);
}
}

そしてJavaScriptでは、エラーと成功のレスポンスに対して別々のコールバックを追加できます。

tsx
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};

JavaScriptにエラーのようなオブジェクトを渡したい場合は、RCTUtils.hRCTMakeErrorを使用してください。現時点では、これはErrorの形をした辞書をJavaScriptに渡すだけですが、React Nativeは将来的に本物のJavaScript Errorオブジェクトを自動生成することを目指しています。また、エラーコールバックに使用されるRCTResponseErrorBlock引数を提供することもできます。これはNSError *オブジェクトを受け入れます。この引数型はTurboModulesではサポートされないことに注意してください。

Promise

ネイティブモジュールはPromiseを解決することもでき、これによりJavaScript、特にES2016のasync/await構文を使用する際のコードが簡潔になります。ネイティブモジュールメソッドの最後のパラメータがRCTPromiseResolveBlockRCTPromiseRejectBlockである場合、対応するJSメソッドはJS Promiseオブジェクトを返します。

上記のコードをコールバックの代わりにPromiseを使用するようにリファクタリングすると、このようになります。

objectivec
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSInteger eventId = createCalendarEvent();
if (eventId) {
resolve(@(eventId));
} else {
reject(@"event_failure", @"no event id returned", nil);
}
}

このメソッドのJavaScript側はPromiseを返します。これは、async関数内でawaitキーワードを使用してそれを呼び出し、その結果を待つことができることを意味します。

tsx
const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'my house',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};

JavaScriptへのイベント送信

ネイティブモジュールは、直接呼び出されることなくJavaScriptにイベントを通知できます。たとえば、ネイティブのiOSカレンダーアプリからのカレンダーイベントが間もなく発生することをJavaScriptに通知したい場合があります。これを行うための推奨される方法は、RCTEventEmitterをサブクラス化し、supportedEventsを実装し、self sendEventWithNameを呼び出すことです。

ヘッダークラスを更新してRCTEventEmitterをインポートし、RCTEventEmitterをサブクラス化します。

objectivec
//  CalendarModule.h

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface CalendarModule : RCTEventEmitter <RCTBridgeModule>
@end

JavaScriptコードは、モジュールの周りに新しいNativeEventEmitterインスタンスを作成することで、これらのイベントを購読できます。

リスナーがいないときにイベントを発行すると、リソースを不必要に消費しているという警告が表示されます。これを避け、モジュールのワークロードを最適化するため(例えば、上流の通知から購読を解除したり、バックグラウンドタスクを一時停止したりする)、RCTEventEmitterサブクラスでstartObservingstopObservingをオーバーライドできます。

objectivec
@implementation CalendarModule
{
bool hasListeners;
}

// Will be called when this module's first listener is added.
-(void)startObserving {
hasListeners = YES;
// Set up any upstream listeners or background tasks as necessary
}

// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
hasListeners = NO;
// Remove upstream listeners, stop unnecessary background tasks
}

- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
if (hasListeners) {// Only send events if anyone is listening
[self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}
}

スレッド

ネイティブモジュールが独自のメソッドキューを提供しない限り、どのスレッドで呼び出されているかについて何の仮定もすべきではありません。現在、ネイティブモジュールがメソッドキューを提供しない場合、React Nativeはそれに対して別のGCDキューを作成し、そこでそのメソッドを呼び出します。これは実装の詳細であり、変更される可能性があることに注意してください。ネイティブモジュールに明示的にメソッドキューを提供したい場合は、ネイティブモジュールで(dispatch_queue_t) methodQueueメソッドをオーバーライドします。例えば、メインスレッドのみのiOS APIを使用する必要がある場合は、次のように指定すべきです。

objectivec
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}

同様に、操作に時間がかかる可能性がある場合、ネイティブモジュールは操作を実行するための独自のキューを指定できます。繰り返しになりますが、現在React Nativeはネイティブモジュールに別のメソッドキューを提供しますが、これは依存すべきではない実装の詳細です。独自のメソッドキューを提供しない場合、将来的には、ネイティブモジュールの長時間実行される操作が、他の無関係なネイティブモジュールで実行されている非同期呼び出しをブロックする可能性があります。例えば、ここのRCTAsyncLocalStorageモジュールは、Reactキューが潜在的に遅いディスクアクセスを待ってブロックされないように、独自のキューを作成します。

objectivec
- (dispatch_queue_t)methodQueue
{
return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}

指定されたmethodQueueは、モジュール内のすべてのメソッドで共有されます。メソッドの1つだけが長時間実行される(または何らかの理由で他のメソッドとは異なるキューで実行する必要がある)場合は、メソッド内でdispatch_asyncを使用して、その特定のメソッドのコードを他のキューで実行し、他のメソッドに影響を与えないようにすることができます。

objectivec
RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Call long-running code on background thread
...
// You can invoke callback from any thread/queue
callback(@[...]);
});
}

モジュール間でのディスパッチキューの共有

methodQueueメソッドはモジュールが初期化される際に一度だけ呼ばれ、その後React Nativeによって保持されます。そのため、モジュール内でそれを利用したい場合を除き、自分でキューへの参照を保持する必要はありません。しかし、複数のモジュール間で同じキューを共有したい場合は、それぞれに対して同じキューインスタンスを保持し、返すようにする必要があります。

依存性の注入

React Nativeは、登録されているすべてのネイティブモジュールを自動的に作成し、初期化します。しかし、例えば依存性を注入するために、独自のモジュールインスタンスを作成し、初期化したい場合があるかもしれません。

これは、`RCTBridgeDelegate`プロトコルを実装するクラスを作成し、そのデリゲートを引数として`RCTBridge`を初期化し、初期化されたブリッジで`RCTRootView`を初期化することによって行うことができます。

objectivec
id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];

RCTRootView *rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:kModuleName
initialProperties:nil];

Swiftのエクスポート

Swiftにはマクロのサポートがないため、React Native内でネイティブモジュールとそのメソッドをJavaScriptに公開するには、もう少し設定が必要です。しかし、動作は比較的同じです。同じCalendarModuleをSwiftクラスとして持っているとしましょう。

swift
// CalendarModule.swift

@objc(CalendarModule)
class CalendarModule: NSObject {

@objc(addEvent:location:date:)
func addEvent(_ name: String, location: String, date: NSNumber) -> Void {
// Date is ready to use!
}

@objc
func constantsToExport() -> [String: Any]! {
return ["someKey": "someValue"]
}

}

クラスと関数がObjective-Cランタイムに正しくエクスポートされるように、@objc修飾子を使用することが重要です。

次に、必要な情報をReact Nativeに登録するプライベートな実装ファイルを作成します。

objectivec
// CalendarModuleBridge.m
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(CalendarModule, NSObject)

RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)

@end

SwiftとObjective-Cに慣れていない方のために説明すると、iOSプロジェクトでこれら2つの言語を混在させる場合、Objective-CファイルをSwiftに公開するために、ブリッジングヘッダーとして知られる追加のブリッジングファイルも必要になります。Xcodeの`File > New File`メニューオプションからSwiftファイルをアプリに追加すると、Xcodeがこのヘッダーファイルの作成を提案してくれます。このヘッダーファイルに`RCTBridgeModule.h`をインポートする必要があります。

objectivec
// CalendarModule-Bridging-Header.h
#import <React/RCTBridgeModule.h>

また、RCT_EXTERN_REMAP_MODULE_RCT_EXTERN_REMAP_METHODを使用して、エクスポートするモジュールやメソッドのJavaScript名を変更することもできます。詳細については、RCTBridgeModuleを参照してください。

サードパーティモジュールを作成する際の重要事項: Swiftを含む静的ライブラリはXcode 9以降でのみサポートされています。モジュールに含まれるiOS静的ライブラリでSwiftを使用する場合にXcodeプロジェクトがビルドされるためには、メインのアプリプロジェクト自体にSwiftコードとブリッジングヘッダーが含まれている必要があります。アプリプロジェクトにSwiftコードが含まれていない場合、回避策として空の.swiftファイルと空のブリッジングヘッダーを1つずつ用意する方法があります。

予約されたメソッド名

invalidate()

ネイティブモジュールは、iOS上で`invalidate()`メソッドを実装することでRCTInvalidatingプロトコルに準拠できます。このメソッドは、ネイティブブリッジが無効化されたとき(例:devmodeでのリロード時)に呼び出されることがあります。ネイティブモジュールに必要なクリーンアップを行うために、必要に応じてこのメカニズムを使用してください。