概要
このガイドでは、React Native Native Bridgeを使用して、PageSense iOS SDKをReact Nativeアプリケーションに連携する方法を説明します。このブリッジにより、React NativeのJavaScriptコードからiOS SDKの関数を呼び出し、モバイルアプリケーション内でFullStack実験機能を使用できます。
このガイドを完了すると、React Nativeアプリケーションから次のPageSense SDK関数をJavaScriptから直接呼び出せるようになります。
React Native Native Bridgeとは
React Native Native Bridgeは、JavaScriptランタイムとネイティブiOSコード間の通信レイヤーです。JavaScriptからネイティブ関数を呼び出し、プラットフォーム固有のSDK機能にアクセスし、ネイティブモジュールから応答を受け取ることができます。
JavaScriptはSwiftと直接やり取りできないため、React Nativeでは中間層としてブリッジが提供されています。ネイティブメソッドはSwiftで実装され、Objective-Cブリッジファイルを通じてReact Nativeに公開されます。
React Native UI
↓
React Nativeラッパー(TypeScript/JavaScript)
↓
React Native Native Bridge
↓
Objective-Cブリッジモジュール(.m)
↓
Swiftネイティブモジュール(.swift)
↓
PageSense iOS SDK
ネイティブモジュールはSwiftで実装され、Objective-Cブリッジファイルを介してReact NativeのJavaScriptレイヤーに公開されます。登録されると、このモジュールにはReact NativeのNativeModules APIからアクセスできます。
前提条件
-
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でワークスペースを開き、次の場所に移動します。
[Target]→[General]→[Frameworks, Libraries and Embedded Content]
-
PageSenseFramework.xcframeworkを追加し、オプションを[Embed & Sign]に設定します。
iOS向けReact Nativeライブラリー
次のReact Nativeライブラリーが必要です。pod installをiosディレクトリーから実行すると、自動的にインストールされます。
-
React-Core
-
React-RCTBridge
-
React-RCTNetwork
-
React-RCTText
-
React-RCTImage
React Native Bridgeを実装するには、次の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でプロジェクトを開き、[Build Settings]に移動します。
-
[Objective-C Bridging Header]を見つけ、値を以下に設定します。
SampleApp/SampleApp-Bridging-Header.h
GoalPropertyのマッピング
iOSでは、PageSense SDKはゴールプロパティを[GoalProperty: String]として受け取ります。ここでGoalPropertyは、PageSenseFrameworkによって公開される、小文字のraw値を持つ列挙型です。React Nativeブリッジは、JavaScriptからゴールプロパティをNSDictionaryとして受け取ります。これは文字列キー/valueのペアで構成され、各小文字の文字列キーを対応するGoalProperty列挙型ケースに、GoalProperty(rawValue:)を使用してマッピングします。
|
JavaScriptキー
|
文字列値
|
iOS GoalProperty列挙型ケース
|
|
GoalProperty.REVENUE
|
'revenue'
|
GoalProperty.revenue
|
ベストプラクティス
-
SDKの初期化は、プロジェクトごとにアプリケーションのライフサイクル中に1回だけ行い、実験を有効化する前に実行してください。
-
SDKの初期化に失敗しても、UIのレンダリングをブロックしないでください。必ずフォールバックUIを使用してください。
-
アプリケーションセッション全体でPageSenseClientインスタンスを保持してください。
-
ユーザーごとに一意のユーザーIDを作成し、全体で再利用してください。
-
ユーザーセッションごとに1回だけPageSenseUserContextを構築し、SDK呼び出し全体で再利用してください。
-
収益ゴールを追跡する場合は、収益額を必ずセント単位の文字列として渡してください。ドル金額に100を掛け、最も近い整数に丸めます。
-
ハードコードされた文字列ではなく、JavaScriptラッパーによってエクスポートされるGoalProperty定数を使用してください。小文字の文字列値は、iOSのGoalProperty列挙型のraw値に直接マッピングされます。
-
nullバリエーションは必ず処理し、安全なデフォルトUIにフォールバックしてください。
-
ネイティブモジュールには、UIの状態ではなくSDK関連のオブジェクトのみを保存してください。
-
特定のユーザー操作またはイベントが正常に完了した後に、ゴールをトリガーしてください。
-
新規連携ではすべて、コンテキストベースのSDK関数(activateExperimentByContext、getVariationNameByContext、trackGoalByContext)を使用してください。