iOS
Swift
WatchKit
AppleWatch
iOS9

Watch Connectivity を使ってiPhone内に保存されているデータを、WatchOS2に持ってくる

More than 3 years have passed since last update.

概要

  • WatchOS2からWatchExtensionがWatchOS上で動くようになるため
  • iOS内に格納されているデータをWatchExtensionから参照することができなくなりました
  • たとえば、Keychainに保存しているアクセストークン、リフレッシュトークンなどの認証情報も、WatchOS2からiOS内のkeychainを参照することはできないため、Watch Connectivityで送信する必要があります

Application Contextを使う場合(OS側にデータ送受信のタイミングを任せる)

  • Application Contextは、キューに送信データを入れておけば、OS側がよしなに通信してくれるものです。
  • 受信キューは上書きされていくため、受信側が受信前に複数送信された場合、最終データしか受け取れません

処理フロー

  1. [iOS]起動時、WatchConnectivityを使ってデータを送信
  2. [WatchOS2]デリゲートメソッドでデータを受信

実装

Watch Connectivityをアクティベート

  • iOS, watch側ともに以下のコードが必要です。appDelegateやExtensionDelegateなどに実装しておきます。
ExtensionDelegate.swift
// Watch Connectivity サポートチェック
if WCSession.isSupported() {
    let session = WCSession.defaultSession()
    session.delegate = self;
    session.activateSession()
}

[iOS側]送りたいデータをApplication Contextで送信する

AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    if WCSession.isSupported() {
        let session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()

        let sendMessage = ["body":"sendApplicationContext"] //送信したいデータを設定
        do {
            try session.updateApplicationContext(sendMessage)
        } catch {
            print("error")
        }
    }

    return true
}

[WatchOS2側]デリゲートメソッドで受信する

  • WCSessionDelegateプロトコルを設定しておく
ExtensionDelegate.swift
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {
  • 受信時の処理を書く
ExtensionDelegate.swift
// application contextの受信時の処理
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
    print("receive::\(applicationContext)")
}

シミュレータで実行

  • WatchKit Appスキーマで実行すると自動でiPhone+watchのシミュレータが起動します
  • 正しく実装されていればログに receive::[body: sendApplicationContext] と出ます
  • appDelegateの実装を applicationWillEnterForeground(application: UIApplication) にも書けば、iOSアプリがバックグラウンドから復帰するたびに同様のログが出力されるのが確認できます
  • シミュレータだと常にペアリング状態なのでApplication Contextでも即時通信されている

Interactive Messagingを使う場合(任意のタイミングで送受信)

  • Interactive Messaging は任意のタイミングで通信が可能
  • 以下の様なフローで実装してみます
  1. [WatchOS2]アプリを起動する
  2. [WatchOS2]iOSにデータを送れ、というメッセージを送る
  3. [iOS]WatchOS2からのメッセージを受け取ったら、データをWatchOS2に返信する
  4. [WatchOS2]取得したデータをログに表示する

実装

[WatchOS2] WatchOS2からiOSに対してメッセージを送る

ExtensionDelegate.swift
func applicationDidFinishLaunching() {

    if WCSession.isSupported() {
        let session = WCSession.defaultSession()
        session.delegate = self;
        session.activateSession()

        if  session.reachable {

            let contents = ["body" : "sendInteractiveMessaging"]

            session.sendMessage(contents, replyHandler: { (replyMessage) -> Void in
                    //iOSからのデータを受信した時の処理
                    print("receive::\replyMessage")
                }) { (error) -> Void in
                    print(error)
            }
        }
    }
}

[iOS] デリゲートメソッドでメッセージを受信し、データをWatchに送信する

  • プロトコルの宣言
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
  • 受信時の挙動を設定
AppDelegate.swift
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {

    // 受信したメッセージ
    print("receiveMessage::\(message)")

    // ここで必要なデータをWatchに送信
    let replyMessage = ["body":"replyInteractiveMessaging"]
    replyHandler(replyMessage)    
}

シミュレータで実行

  • [body: replyInteractiveMessaging] というログが出てくるのが確認できます
  • あまり大きいデータは送れないようです。数百バイト程度?超えると エラーになります
  • Watch起動時にInteractive Messagingを使うのは、シミュレータでは動きましたが実機では動作しませんでした。。原因不明

まとめ

  • ペアリングされていて、通信可能な状態だと、ApplicationContextでも即時に送受信が行われる
  • WatchOS が任意のタイミングで iOS からデータを取得したい場合は Interactive Messaging を使う
  • Interactive Messaging は Watch -> iOS の場合のみ iOS側のアプリが起動していなくても、受信時のコードが動くようです