LoginSignup
14
13

More than 5 years have passed since last update.

RealmデータをwatchOSとiOSとの間でやりとりする(3)

Last updated at Posted at 2016-11-04

はじめに

(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()の中でアクティベートする処理を記述します。

ViewController.swift
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, *)をつける必要があります。

ViewController.swift
@available(iOS 9.3, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("activationDidComplete")
    }

watchOS側を実装する

iOS側と同様にWCSessionDelegateを宣言します。

InterfaceController.swift
class InterfaceController: WKInterfaceController, WCSessionDelegate {

起動時に処理したいので、awake()の中でアクティベートする処理を記述します。

InterfaceController.swift
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, *)をつける必要があります。

InterfaceController.swift
@available(watchOS 2.2, *)
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
    print("activationDidComplete")
}

ファイルを送信する

セッションをアクティベートできたので、今度は、iOS側のRealmファイルをwatchOS側へfileTransfer()を用いて送信します。

iOS側で送信したい時点でこの処理を行います。

ViewController.swift
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系に渡しています。

InterfaceController.swift
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()にデータを持たせて送信を実行する。

ViewController.swift
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)が呼ばれるので、受信データを使ってしかるべき処理をする。

InterfaceController.swift
  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側を対応させることも可能です。

関連リンク

RealmデータをwatchOSとiOSとの間でやりとりする(1)

14
13
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13