【Swift/Apple Watch】iOSへデータを送受信するメソッドの違いと使い方!
この記事からわかること
- SwiftでApple Watchアプリを開発する方法
- iOSへデータを送信/受信するには?
- sendMessageメソッドの使い方
- transferUserInfo/updateApplicationContext/transferFile(_:metadata:)の違い
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.0
- watchOS:10.0
- Swift:5.9
- macOS:Sonoma 14.1
iOS↔︎watchOS間でデータを送受信する方法
iOSとwatchOSの連携アプリでは両OS間でデータをやり取りすることが可能です。送受信するための方法は4種類用意されており、それぞれ挙動やできることが異なるので違いと使い方をまとめていきたいと思います。使用するメソッドはWatchConnectivity
フレームワークから提供されているおり、iOS/watchOSともに共通の実装で行うことが可能です。
データを送信するため役割を持っているのは以下の4種類です。
- sendMessage
- transferUserInfo
- updateApplicationContext
- transferFile
transferFile
メソッドを除いてですが両OS間でのデータのやり取りは辞書型[String: Any]
形式で送受信が可能になっています。
またデータの受信側はWCSessionDelegate
に準拠させることで各デリゲートメソッドよりデータを受信できるようになります。
送信できるデータ型
sendMessage
やtransferUserInfo
などのデータを送信するメソッドは[String: Any]
形式の辞書型でデータを送信できますが、サポートしているのはプロパティタイプリストのデータ型(基本的なデータ型)のみのようです。そのため独自のクラスなどはサポートされていないのでJSONなどに変換してString型として送信するのが定石です。
公式リファレンス:Property List Types and Objects
sendMessage
メソッドの定義を確認してみるとコメントの部分に「メッセージ ディクショナリはプロパティ リスト タイプのみを受け入れることができます」と記述されていました。
/** クライアントはこのメソッドを使用して、対応するアプリにメッセージを送信できます。特定のメッセージに対する応答を受け取りたいクライアントは、replyHandler ブロックを渡す必要があります。メッセージを送信できない場合、または応答を受信できない場合は、errorHandler ブロックがエラーで呼び出されます。 ReplyHandler と errorHandler の両方が指定されている場合は、そのうちの 1 つだけが呼び出されます。メッセージは送信アプリの実行中にのみ送信できます。メッセージが送信される前に送信側アプリが終了すると、送信は失敗します。対応するアプリが実行されていない場合、メッセージの受信時に対応するアプリが起動されます (iOS 対応アプリのみ)。メッセージ ディクショナリはプロパティ リスト タイプのみを受け入れることができます。 */
open func sendMessage(_ message: [String : Any], replyHandler: (([String : Any]) -> Void)?, errorHandler: ((Error) -> Void)? = nil)
sendMessageメソッド
open func sendMessage(_ message: [String : Any], replyHandler: (([String : Any]) -> Void)?, errorHandler: ((Error) -> Void)? = nil)
sendMessage
はペアリングされたアクティブなデバイスにメッセージを送信するメソッドです。引数replyHandler
には受け取り側にデータが到達後に実行したい処理を、引数errorHandler
では送信エラーが発生した時に実行したい処理を渡すことができます。
送信処理の実装例
let dataDic: [String: String] = ["response": text]
self.session.sendMessage(dataDic, replyHandler: { message in
print("正常に送信し、相手が受信しました", message)
} ) { error in
print(error)
}
受信処理の実装例
受信側はsession(_: WCSession, didReceiveMessage: , replyHandler:)
から受け取ることができます。受信したデータは[String : Any]
型になっているので送信したキーから値を取り出し、適切な型にキャストすることで中身を取得することができます。replyHandler
で受け取ったデータを返すことで送信側に受信したことを知らせることができます。
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
replyHandler(message) // これをしないとsendMessage(送信側)のreplyHandlerは呼ばれない
guard let result = message["response"] as? String else { return }
print(result)
}
replyHandlerにnilを渡すとデリケートメソッドが変化
引数replyHandler
にはnil
を渡すことも可能です。その場合は受信するデリゲートメソッドが変化するので注意してください。
送信処理の実装例
self.session.sendMessage(dataDic, replyHandler: nil) { error in
print(error)
}
受信処理の実装例
受信側はsession(_: WCSession, didReceiveMessage:)
から受け取ることができます。
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("replyHandler:nil")
guard let result = message["response"] as? String else { return }
print(result)
}
watchOS側から送信するとiOSがバックグラウンドで起動?
WatchKit 拡張機能がアクティブで実行中にこのメソッドを呼び出すと、対応する iOS アプリがバックグラウンドで起動され、アクセス可能になります。 iOS アプリからこのメソッドを呼び出しても、対応する WatchKit 拡張機能は起動されません。
公式ドキュメントには上記のように記述されていました。動作を確認してみたところ、iOS側が停止している状態でもwatchOS側からsendMessage
を送信するとreplyHandler
には受信が成功した通知が取得できました。
transferUserInfo
func transferUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer
transferUserInfo
もペアリングされたデバイスに辞書形式でデータを送信できるメソッドです。しかしsendMessage
とは異なりキューにデータをスタックしていき、相手が受信可能になっていれば送信する仕組みになっています。
そのため相手のデバイスが非アクティブ(停止している)状態でもデータの送信が可能(正確にはキューイング)になっており、相手がアクティブになったタイミングでデータが送信され始めるため、相手側はアクティブになったと同時にデータを受信することができます。
このメソッドには相手が受信したかどうかやエラーをハンドリングする手段がないのでデータを確実に一方的に送信したい場合などに活用することができます。連続で実行すればどんどんキューが溜まっていきアクティブになると順番通りに送信されていくようです。
送信処理の実装例
let requestDic: [String: String] = ["request": true]
self.session.transferUserInfo(requestDic)
受信処理の実装例
受信側はsession(_:, didReceiveUserInfo:)
から受け取ることができます。
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
guard let result = userInfo["request"] as? Bool else { return }
if result {
send("request")
}
}
シミュレーターでは動作しない
またこのメソッドはシミュレーターでは動作確認することができないと公式に記述されているので実機を使用してテストする必要があるので注意してください。
updateApplicationContext
公式リファレンス:updateApplicationContextメソッド
func updateApplicationContext(_ applicationContext: [String : Any]) throws
updateApplicationContext
もペアリングされたデバイスに辞書形式でデータを送信できるメソッドです。transferUserInfo
と同じように相手がバックグラウンドなどの状態でもデータを送信することが可能になっています。
transferUserInfo
との違いはキューに蓄積せずに常に更新することです。連続で実行してもアクティブになった時に送信されるのは最後に送信したデータになります。
このメソッドには相手が受信したかどうかやエラーをハンドリングする手段がないのでデータを確実に一方的に送信したい場合などに活用することができます。連続で実行すればどんどんキューが溜まっていきアクティブになると順番通りに送信されていくようです。
またこのメソッドはシミュレーターでも正常に動作します。
送信処理の実装例
let dataDic: [String: String] = ["response": text]
do {
try session.updateApplicationContext(dataDic)
} catch {
print(error.localizedDescription)
}
受信処理の実装例
受信側はsession(_:, didReceiveApplicationContext:)
から受け取ることができます。
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
guard let result = applicationContext["response"] as? String else { return }
print(result)
}
transferFile
func transferFile(_ file: URL, metadata: [String : Any]?) -> WCSessionFileTransfer
transferFile
は他の送信メソッドと異なりペアリングされたデバイスに指定されたファイルを送信できるメソッドです。非常に大きなデータを送信する際などに活用することができます。
こちらも相手がバックグラウンドでも送信が可能であり、transferUserInfo
と同じくキューに溜まっていく仕組みのようです。データが大きいためパフォーマンスと充電を考慮し配信速度が調整される場合があります。そのためoutstandingFileTransfers
プロパティから未配信のファイルを参照できるようになっています。
var outstandingFileTransfers: [WCSessionFileTransfer] { get }
またこのメソッドもシミュレーターでは動作確認することができないと公式に記述されているので実機を使用してテストする必要があるので注意してください。
送信処理の実装例
self.session.transferFile(fileURL, metadata: nil)
受信処理の実装例
受信側はsession(_:, didReceive:)
から受け取ることができます。
func session(_ session: WCSession, didReceive file: WCSessionFile) {
let data = try? Data.init(contentsOf: file.fileURL)
}
各メソッドの違いとまとめ
- sendMessage・・・辞書形式でデータを送信/相手が基本アクティブ/非アクティブなら成功しない(watchOS→iOSは別)
- transferUserInfo・・・辞書形式でデータを送信/相手がアクティブでない場合キューにスタックされていく/アクティブになったら送信
- updateApplicationContext・・・辞書形式でデータを送信/相手がアクティブでない場合に更新して保管されていく/アクティブになったら送信
- transferFile・・・ファイルを送信/相手がアクティブでない場合キューにスタックされていく/アクティブになったら送信
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。