概要
このガイドでは、React Nativeネイティブブリッジを使用して、PageSense iOS SDKをReact Nativeアプリケーションに統合する方法について説明します。このブリッジにより、React NativeのJavaScriptコードからiOS SDKの関数を呼び出し、モバイルアプリケーション内でフルスタックのテスト機能を利用できます。
このガイドを完了すると、React Nativeアプリケーションから、JavaScriptを使用してPageSense SDKの次の関数を直接呼び出せるようになります。
React Nativeネイティブブリッジとは
React Nativeネイティブブリッジは、JavaScriptランタイムとiOSのネイティブコードの間にある通信レイヤーです。これにより、JavaScriptからネイティブ関数を呼び出し、プラットフォーム固有のSDK機能にアクセスして、ネイティブモジュールから応答を受け取ることができます。
JavaScriptはSwiftと直接やり取りできないため、React Nativeではブリッジが仲介役となります。ネイティブメソッドはSwiftで実装され、Objective-Cブリッジファイルを通じてReact Nativeに公開されます。
React Native UI
↓
React Nativeラッパー(TypeScript/JavaScript)
↓
React Nativeネイティブブリッジ
↓
Objective-Cブリッジモジュール(.m)
↓
Swiftネイティブモジュール(.swift)
↓
PageSense iOS SDK
ネイティブモジュールはSwiftで実装され、Objective-Cブリッジファイルを通じてReact NativeのJavaScriptレイヤーに公開されます。登録後、このモジュールにはReact NativeのNativeModulesAPIからアクセスできます。
前提条件
-
Node.js
-
npm
-
React Native CLI
-
Xcode
-
CocoaPods
bash
node-v
npm-v
pod --version
PageSense iOS SDKのインストール
PageSense iOS SDKはXCFrameworkとして配布されています。
-
-
展開したフレームワークをiOSプロジェクトのフォルダーに配置します。
/ProjectRoot/ios/Frameworks/PageSenseFramework.xcframework
-
Xcodeでワークスペースを開き、次の場所に移動します。
[ターゲット]→[一般]→[フレームワーク、ライブラリ、埋め込みコンテンツ]
-
「PageSenseFramework.xcframework」を追加し、選択肢を[埋め込みと署名]に設定します。
iOS用React Nativeライブラリ
次のReact Nativeライブラリが必要です。これらは、pod installをiosディレクトリーから実行すると、自動的にインストールされます。
-
React-Core
-
React-RCTBridge
-
React-RCTNetwork
-
React-RCTText
-
React-RCTImage
React Nativeブリッジを実装するには、次のiOSフレームワーク依存関係が必要です。
-
React
-
RCTBridgeModule
-
Foundation
ネイティブブリッジモジュールの作成
iOSネイティブブリッジは、SwiftネイティブモジュールとObjective-Cブリッジファイルの2つの部分で実装します。
パート1 — Swiftネイティブモジュール
「PageSenseSDKModule.swift」をiOSアプリケーションのディレクトリー内に作成します。
- /ProjectRoot/ios/SampleApp/PageSenseSDKModule.swift
- swift
- import Foundation
- import React
- import PageSenseFramework
- @objc(PageSenseSDKModule)
- class PageSenseSDKModule: NSObject {
- private varclient: PageSenseClient?
- private varisInitialized: Bool= false
- private static let LOG_TAG= '[PageSenseSDK]'
- @objc
- static func requiresMainQueueSetup()-> Bool {
- return false
- }
- @objc
- func initialiseSDK(
- _accountId: String,
- sdkKey: String,
- projectName: String,
- resolver resolve: @escaping RCTPromiseResolveBlock,
- rejecter reject: @escaping RCTPromiseRejectBlock
- ) {
- if self.isInitialized { resolve(true); return }
- letpageSenseSDKOptions = PageSenseSDKOptions()
- pageSenseSDKOptions.logLevel = .DEBUG
- pageSenseSDKOptions.pollingInterval = 5
- PageSense.shared.createNewPageSenseClient(
- sdkAccountIDForDC:accountId,
- sdkKey:sdkKey,
- projectname:projectName,
- sdkOptions:pageSenseSDKOptions
- ) {pagesenseclient in
- guard letclient = pagesenseclient else { resolve(false); return }
- self.client = client
- self.isInitialized = true
- resolve(true)
- }
- }
- @objc
- func activateExperimentByContext(
- _experimentName: String,
- userContext: NSDictionary,
- resolver resolve: @escaping RCTPromiseResolveBlock,
- rejecter reject: @escaping RCTPromiseRejectBlock
- ) {
- guard letclient = self.client,
- letpageSenseUserContext = validateAndBuildUserContext(userContext) else {
- resolve(nil); return
- }
- client.activateExperiment(experimentName:experimentName,userContext:pageSenseUserContext) {variation in
- resolve(variation)
- }
- }
- @objc
- func getVariationNameByContext(
- _experimentName: String,
- userContext: NSDictionary,
- resolver resolve: @escaping RCTPromiseResolveBlock,
- rejecter reject: @escaping RCTPromiseRejectBlock
- ) {
- guard letclient = self.client,
- letpageSenseUserContext = validateAndBuildUserContext(userContext) else {
- resolve(nil); return
- }
- resolve(client.getVariationName(experimentName:experimentName,userContext:pageSenseUserContext))
- }
- @objc
- func trackGoalByContext(_goalName: String,userContext: NSDictionary) {
- guard letclient = self.client,
- letpageSenseUserContext = validateAndBuildUserContext(userContext) else { return }
- client.trackGoal(goalName:goalName,userContext:pageSenseUserContext)
- }
- @objc
- func trackGoalWithPropertiesByContext(
- _goalName: String,
- userContext: NSDictionary,
- goalProperties: NSDictionary
- ) {
- guard letclient = self.client,
- letpageSenseUserContext = validateAndBuildUserContext(userContext),
- letgoalPropertiesMap = validateAndConvertGoalPropertiesNonEmpty(goalProperties) else { return }
- client.trackGoal(goalName:goalName,userContext:pageSenseUserContext,goalProperties:goalPropertiesMap)
- }
- private func validateAndBuildUserContext(_userContextDict: NSDictionary)-> PageSenseUserContext? {
- guard letuserId = userContextDict['userId'] as? String,
- !userId.trimmingCharacters(in: .whitespaces).isEmpty else { return nil }
- varuserAttributes: [String: String]= [:]
- if letrawAttrs = userContextDict['userAttributes'] as? NSDictionary {
- for (key,value) inrawAttrs {
- guard letk = key as? String, letv = value as? String else { return nil }
- userAttributes[k]= v
- }
- }
- return PageSenseUserContext(userId:userId,userAttributes:userAttributes)
- }
- private func validateAndConvertGoalPropertiesNonEmpty(_goalPropertiesDict: NSDictionary)-> [GoalProperty: String]? {
- ifgoalPropertiesDict.count == 0 { return nil }
- varresult: [GoalProperty: String]= [:]
- for (key,value) ingoalPropertiesDict {
- guard letkeyString = key as? String,
- letgoalProperty = GoalProperty(rawValue:keyString),
- letvalueString = value as? String else { return nil }
- result[goalProperty]= valueString
- }
- returnresult
- }
- }
パート2 — Objective-Cブリッジファイル
同じディレクトリーにPageSenseSDKModule.mを作成します。
- /ProjectRoot/ios/SampleApp/PageSenseSDKModule.m
- objc
- #import <React/RCTBridgeModule.h>
- @interface RCT_EXTERN_MODULE(PageSenseSDKModule,NSObject)
- RCT_EXTERN_METHOD(initialiseSDK:(NSString *)accountId
- sdkKey:(NSString *)sdkKey
- projectName:(NSString *)projectName
- resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject)
- RCT_EXTERN_METHOD(activateExperimentByContext:(NSString *)experimentName
- userContext:(NSDictionary *)userContext
- resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject)
- RCT_EXTERN_METHOD(getVariationNameByContext:(NSString *)experimentName
- userContext:(NSDictionary *)userContext
- resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject)
- RCT_EXTERN_METHOD(trackGoalByContext:(NSString *)goalName
- userContext:(NSDictionary *)userContext)
- RCT_EXTERN_METHOD(trackGoalWithPropertiesByContext:(NSString *)goalName
- userContext:(NSDictionary *)userContext
- goalProperties:(NSDictionary *)goalProperties)
- @end
Objective-Cブリッジヘッダーの追加
SwiftとObjective-Cの間の通信を有効にするには、次の手順を実行します。
-
次の場所にブリッジヘッダーファイルを作成します。
/ProjectRoot/ios/SampleApp/SampleApp-Bridging-Header.h
-
Xcodeでプロジェクトを開き、[ビルド設定]に移動します。
-
[Objective-Cブリッジヘッダー]を見つけ、値を次のように設定します。
SampleApp/SampleApp-Bridging-Header.h
GoalPropertyのマッピング
iOSでは、PageSense SDKは目標プロパティを[GoalProperty: String]形式で受け取ります。ここで、GoalPropertyはPageSenseFrameworkによって公開されている列挙型で、raw値は小文字です。React Nativeブリッジは、JavaScriptから目標プロパティを文字列のキーと値のペアで構成されたNSDictionaryとして受け取り、小文字の各文字列キーを、対応するGoalProperty列挙型のケースにGoalProperty(rawValue:)を使用してマッピングします。
|
JavaScriptキー
|
文字列値
|
iOSのGoalProperty列挙型のケース
|
|
GoalProperty.REVENUE
|
'revenue'
|
GoalProperty.revenue
|
ベストプラクティス
-
プロジェクトのSDK初期化は、アプリケーションのライフサイクルごとに1回のみ、テストを有効化する前に行ってください。
-
SDKの初期化に失敗しても、UIのレンダリングをブロックしないでください。必ずフォールバックUIを使用してください。
-
PageSenseClientインスタンスは、アプリケーションセッション全体で保持してください。
-
ユーザーごとに一意のユーザーIDを作成し、全体で再利用してください。
-
PageSenseUserContextはユーザーセッションごとに1回だけ構築し、SDKの呼び出し全体で再利用してください。
-
売上目標を追跡する場合は、売上の値をセント単位の文字列として必ず渡してください。ドル金額に100を掛け、最も近い整数に丸めます。
-
ハードコードされた文字列ではなく、JavaScriptラッパーからエクスポートされるGoalProperty定数を使用してください。小文字の文字列値は、iOSのGoalProperty列挙型のraw値に直接マッピングされます。
-
バリエーションがnullの場合は、常に安全なデフォルトUIにフォールバックしてください。
-
ネイティブモジュールには、UIの状態ではなく、SDK関連のオブジェクトのみを保存してください。
-
特定のユーザー処理またはイベントが正常に完了した後に、目標をトリガーしてください。
-
すべての新規連携では、コンテキストベースのSDK関数(activateExperimentByContext、getVariationNameByContext、trackGoalByContext)を使用してください。