#はじめに
(RealmデータをwatchOSとiOSとの間でやりとりする(2)の続きになります。)
watchOSとiOSとの間でRealmのデータをやりとりする実装方法について述べます。
watchOS2+、iOS9+、Swift 3を前提に話を進めます。
動作するコードをサンプルプロジェクトとしてGitHubに公開しました。実機でランすれば、速度感を体験できます。
#WCSessionをアクティベートする
iOS/watchOS間でデータをやりとりするために、WatchConnectivityフレームワークのWCSessionを使います。
最初に、iOS/watchOSのそれぞれでWCSessionをアクティベートします。
##iOS側の実装をする
アクティベートの成否などを取得できるdelegateメソッドを使いたいので、WCSessionDelegateを宣言します。
class ViewController: UIViewController, WCSessionDelegate {
viewDidLoad()の中でアクティベートする処理を記述します。
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
これで、起動時にWCSessionをアクティベートできるようになりました。WCSessionDelegateの指定もしたので、deleagteメソッドが呼ばれるようになる準備も完了しました。
アクティベートが完了した場合に呼ばれるsession(_:activationDidCompleteWith:error:)はiOS9.3+なので、それ以前をターゲットに含める場合(iOS9.0を対象にしているときなど)は@available(iOS 9.3, *)
をつける必要があります。
@available(iOS 9.3, *)
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("activationDidComplete")
}
##watchOS側を実装する
iOS側と同様にWCSessionDelegateを宣言します。
class InterfaceController: WKInterfaceController, WCSessionDelegate {
起動時に処理したいので、awake()の中でアクティベートする処理を記述します。
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if WCSession.isSupported() {
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
さきほどと同様に、session(_:activationDidCompleteWith:error:)はwatchOS2.2+なので、@available(watchOS 2.2, *)
をつける必要があります。
@available(watchOS 2.2, *)
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("activationDidComplete")
}
ファイルを送信する
セッションをアクティベートできたので、今度は、iOS側のRealmファイルをwatchOS側へfileTransfer()を用いて送信します。
iOS側で送信したい時点でこの処理を行います。
if let path = Realm.Configuration().fileURL {
WCSession.default().transferFile(path, metadata: nil)
}
サンプルの中では、textFieldの変更時に毎回送信したいので、textFieldのrealmTextFieldEditingChanged()アクションにて、送信しています。加えて、iOS側アプリ起動時に一度送信するためにviewDidLoad()でも送信しています。
textFieldの編集毎に実行することにすると、短時間に大量のtransferFile()が実行される可能性があります。この点、公式リファレンスの冒頭で"those transfers happen opportunistically in the background."と書かれているように、実際は毎回送信が発生するわけではありません。
ファイルを受信する
iOS側から送信されたファイルを、watchOS側で受信します。
InterfaceControllerは前述のとおりWCSessionDelegateを宣言していますので、そのdelegateメソッドであるsession(_ session: WCSession, didReceive file: WCSessionFile)を利用できるようになっています。
ファイルを受信した時に呼ばれるので、その中でRealmの標準ファイルを逐次、入れ替える処理を書きます。
サンプルでは、ファイルを入れ替えた後で、データを取得してUI系に渡しています。
func session(_ session: WCSession, didReceive file: WCSessionFile) {
var config = Realm.Configuration()
config.fileURL = file.fileURL
Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()
if let firstField = realm.objects(Field.self).first{
realmLabel.setText(firstField.text)
}
}
これで、iOS側のtextFieldが変更される度にRealmデータベースの中にデータを格納し、そのファイル全体をWCSessionのtransferFileをしてiOS側からwatchOS側に送信して、これを受け取ったwatchOS側でRealmの標準ファイルを入れ替えた後、表示系を刷新できるようになりました。
サンプルプロジェクトとしてGitHubに公開していますので、全体をもう一度ご確認ください。
#sendMessageとの比較
以上に見たのは、WCSesionが提供するバックグラウンド送信の一つtransferFile()を用いてRealmファイルを送信する方法でした。
パフォーマンスの比較のために、相互メッセージ(Interactive messaging)の方法として用意されているsendMessage()の方法もサンプルに実装しました。
sendMessage()は、watchOS 2 の Watch Connectivity を使ってみたに紹介されている通り、watchOS側がフォアグラウンド状態になっている必要がありますが、その場で送信できる方法です。
実装の流れとしては、次のようになります。
1.. WCSessionをアクティベートする。
2.. iOS側でsendMessage()にデータを持たせて送信を実行する。
func sendMessage(){
if WCSession.default().isReachable {
let applicationDict = ["text": messageTextField.text ?? ""]
WCSession.default().sendMessage(applicationDict,
replyHandler: { replyDict in print(replyDict) },
errorHandler: { error in print(error.localizedDescription)})
}
}
3.. watchOS側でWCSessionDelegateメソッドのsession(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void)
が呼ばれるので、受信データを使ってしかるべき処理をする。
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
if let text = message["text"] as! String?{
messageLabel.setText(text)
}
replyHandler(["reply" : "OK"])
}
ここでは、replyHandlerとして、["reply" : "OK"]
を返す実装になっています。iOS側のViewController.swiftのsendMessage()のreplyHandler{}にこの値が渡るので、成功時にデバッガコンソールに["reply" : "OK"]
の内容が表示されます。
#逆向きのデータ送信について
WCSessionはiOS→watchOSだけでなく、逆向きのwatchOS→iOSのデータ送信にも対応しています。ですので、watchOS側のRealm変更にiOS側を対応させることも可能です。