Edited at
iOSDay 19

WCSession.sendMessageに潜む罠


はじめに

このエントリは iOS Advent Calendar 2018 の19日目の投稿です。(遅くなりましてすみません...)

当方iOSの技術者と言いながら最近iOSの開発があまりできていませんが、頑張って書いていきたいと思います!


やりたいこと

部長「iPhoneとApple Watchで何かしらのメッセージやりとりできるようにしてくんない?」

わたし「めんどくさ(わかりました)」


事の経緯

上記の通りiPhoneからApple Watchにメッセージを送る必要があった。

それっぽいリファレンスを発見できたので、軽い気持ちで見てみたらメソッドを見ていると以下のことがわかった。


  • メッセージは辞書型で必須

  • replyHandlerとerrorHandlerはnilが許容されている。(成功時/失敗時の処理はなくてもいい)

open func sendMessage(_ message: [String : Any], replyHandler: (([String : Any]) -> Swift.Void)?, errorHandler: ((Error) -> Swift.Void)? = nil)

なのでメッセージを送る時に以下のようにした。


ViewController.swift

self.session.sendMessage(

["message" : "7/25は高森藍子の誕生日!"],
replyHandler: nil,
errorHandler: { (error) in do {
// エラー発生
print(error)
}}
)

そうすると警告文が発生していた。

どうやら実装に不備があったらしい。

2018-06-22 10:00:00.000000 SampleProject WatchKit Extension[17803:213358] [WC] -[WCSession _onqueue_notifyOfMessageError:withErrorHandler:] errorHandler: NO with WCErrorCodeDeliveryFailed

似たような事象にあっている人を見つけた。

Apple Watchで寿司をまわしてGetWildする

replyHandlerはnilを許容しているのに、nilを渡すと動かなくなるらしい。

以下のように実装したら無事送信が成功するようになった。

relpyHandlerがなんでnil許容してんだよ...


ViewController.swift

self.session.sendMessage(

["message" : "7/25は高森藍子の誕生日!"],
replyHandler: { (reply) in do {
// 空でもいいので置いとく必要があるがせっかくなのでお祝いしないと。
print("藍子誕生日おめでとう!")
}},
errorHandler: { (error) in do {
// エラー発生
print(error)
}}
)

Apple Watch側でもメッセージ受け取ったらreplayHandlerに何かしら返す必要がある。


AppleWatchViewController.swift

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {

// nilを設定すると送り手側のreplyHandlerが呼ばれない=送信完了のステータスにならない。
// replyHandler(nil)

// なんでもいいから送っとけ
replyHandler(["message" : "お祝いしないといけないね!"])
}



ソース全文


ViewController.swift

//

// ViewController.swift
// SampleProject
//
// Created by satoshi baba <s_baba@currentia.co.jp> on 2018/12/15.
// Copyright © 2018年 Currentia.inc. All rights reserved.
//

import UIKit
import WatchConnectivity

class ViewController: UIViewController {

/// Apple Watchの接続用のセッション
let session:WCSession = WCSession.default

override func viewDidLoad() {
super.viewDidLoad()
self.initWCSession()
}

func initWCSession() {
if WCSession.isSupported() {
self.session.delegate = self
self.session.activate()
}
else{
// サポートされていない時の処理は省略
}
}

func sendMessage() {
DispatchQueue.main.async {
self.session.sendMessage(
["message" : "7/25は高森藍子の誕生日!"],
replyHandler: { (reply) in do {
// 空でもいいので置いとく必要がある。
print("藍子誕生日おめでとう!")
}},
errorHandler: { (error) in do {
// エラー発生
print(error)
}}
)
}
}
}

extension ViewController: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
switch(activationState){
case .activated:
// アクティブになったのでメッセージを送る
self.sendMessage()
case .inactive:
// 省略
case .notActivated:
// 省略
}
}

func sessionDidBecomeInactive(_ session: WCSession) {
// 省略
}

func sessionDidDeactivate(_ session: WCSession) {
// 省略
}
}