WCSession
メソッドsendMessage:replyHandler:errorHandler:
を介してiOSアプリと通信するWatch OS 2アプリケーションがあります
IOSアプリケーションは正しく応答しますが、ドメインWCErrorDomain
のコード7014
でエラーが発生することがあります:「ペイロードを配信できませんでした」
これは、iOSアプリケーションがフォアグラウンドでない場合により頻繁に発生します。
私はこの問題の解決策を見つけられません、私はあなたの一人がこの問題の解決策を知っていることを望みます
IOS10ベータ6とGMに問題があり、Swift3を使用している場合、解決策はiOSアプリのデリゲート関数ヘッダーを次のように変更することです。
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
AnyObjectタイプの代わりに@escapingとAnyに注意してください。
私の場合、両方のデリゲートを実装する必要がありました。
1つなし任意replyHandler
func session(_ session: WCSession,
didReceiveMessage message: [String : Any])
一つwithreplyHandler
func session(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: @escaping ([String : Any]) -> Void)
replyHandler
なしでメッセージを送信すると、最初のデリゲートが実行されます。replyHandler
を使用してメッセージを送信すると、2番目のデリゲートが実行されます。
メッセージだけを送信する場合もあれば、メッセージを送信して相手からの返信を期待する場合もありました。
しかし...私は2番目のデリゲートのみを実装していました-_-
とにかく、最終的に重複コードを減らすために、私は一般的な方法を実装し、次のようになりました:
func session(_ session: WCSession,
didReceiveMessage message: [String : Any]) {
handleSession(session,
didReceiveMessage: message)
}
func session(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: @escaping ([String : Any]) -> Void) {
handleSession(session,
didReceiveMessage: message,
replyHandler: replyHandler)
}
//Helper Method
func handleSession(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: (([String : Any]) -> Void)? = nil) {
//Common logic
}
Watch OS 4
これを試してみてください、これは私の問題を修正しました。 InterfaceController内に、データを電話に渡すための次のメソッドを追加します。
-(void)sendDataToPhone:(NSDictionary* _Nonnull)dictData
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
if(session.reachable)
{
[session sendMessage:dictData replyHandler: ^(NSDictionary<NSString *,id> * __nonnull replyMessage) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@".....replyHandler called --- %@",replyMessage);
// Play a sound in watch
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess];
});
}
errorHandler:^(NSError * __nonnull error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Error = %@",error.localizedDescription);
});
}
];
}
else
NSLog(@"Session Not reachable");
}
else
NSLog(@"Session Not Supported");
}
#pragma mark - Standard WatchKit delegate
-(void)sessionWatchStateDidChange:(nonnull WCSession *)session
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
}
}
電話側で、時計からデータを受信するための次のコードを追加します。
DidFinishLaunchingWithOptionsに以下を追加します。
// Allocating WCSession inorder to communicate back to watch.
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
}
次に、WCSessionDelegateを追加します。
#pragma mark - WCSession Delegate
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler
{
if(message){
NSData *receivedData = [message objectForKey:@"AudioData"];
NSDictionary* response = @{@"response" : [NSString stringWithFormat:@"Data length: %lu",(unsigned long)receivedData.length]} ;
replyHandler(response);
}
}
#pragma mark - Standard WatchKit Delegate
-(void)sessionWatchStateDidChange:(nonnull WCSession *)session
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
if(session.reachable){
NSLog(@"session.reachable");
}
if(session.paired){
if(session.isWatchAppInstalled){
if(session.watchDirectoryURL != nil){
}
}
}
}
}
これがあなたに役立つことを願っています:)
申し訳ありませんが、コメントにコメントするのに十分な評判がありません。私の問題はPeter Robertの回答で解決されました:Swift 3でWCErrorCodeDeliveryFailedが表示され、解決策はreplyHandlerでAnyObjectをAnyに変更するだけでした。
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
//code
replyHandler (answer as [String : Any])
}
アプリライフサイクルの後半で同じ問題が発生し、WCSessionの初期化(デリゲートの設定とアクティブ化)を移動することで問題が解決しました。
アプリデリゲートのdidFinishLaunchingでWCSessionをアクティブ化しましたが、そこで通信を切断しました。アプリの後半でWCSessionの初期化を移動すると、通信が再び機能するようになりました。
私の場合、WCSessionDelegate(iOS side)を別のクラスに入れ、ローカル変数として初期化します。それをglobal instance変数に変更すると、問題が解決しました。
だから私のiOSコードは:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
SessionHandler()
}
それを機能させるために以下に変更しました:
var handler: SessionHandler!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
handler = SessionHandler()
}
デリゲートが正しく接続されていることを確認しますか?
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
注:確認session.delegate = self;
に設定します。
アプリに取り組んでいて、まったく同じ動作をします。私は自分のコードのいたるところを調べて、何も問題がないことを確信しています。私の推測では、これはWatchConnectivity
のバグであると考えられます。
現在のエラーハンドラーの回避策は、この特定のエラーに関するデータをリロードしようとするだけです。あまり美しくありませんが、問題なく動作します。
同様のことを試してみませんか?
_func messageErrorHandler(error: NSError) {
isLoading = false
print("Error Code: \(error.code)\n\(error.localizedDescription)")
// TODO: WTF?. Check future releases for fix on error 7014, and remove this...
if error.code == 7014 {
// Retry after 1.5 seconds...
retryTimer = NSTimer.scheduledTimerWithTimeInterval(
NSTimeInterval(1.5), target: self, selector: "reloadData", userInfo: nil, repeats: false)
return
}
displayError("\(error.localizedDescription) (\(error.code))",
message: "\(error.localizedDescription)")
}
_
更新:
WatchConnectivity
を使用している人向け。 _session.reachable
_変数をテストするには、同様の「ハック」が必要です。
私のアプリは、セッションが到達可能になる前にメッセージを送信できることに気づきました。そのため、実際にユーザーに電話が届かないことをユーザーに伝える前に、数回データをリロード(メッセージを再送信)しようとします。
更新2:上記の例は.sessionWatchStateDidChange()
を使用しているため、接続ackを待機していないために.sendMessage()
が早くトリガーされないことが問題ではありません。毎回発生するわけではないので、これはバグであるに違いありません。100メッセージごとに1のようにひっくり返ります。
応答コードを最初に実行することでこの問題が解決されることを発見しました(タイムアウトが原因である可能性がありますか?)。
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: ([String : Any]) -> Void) {
print("Message - received")
//Send reply
let data = ["receivedData" : true]
replyHandler(data as [String : AnyObject])
}
セッションが常にアクティブであることを確認してください。たとえば、テストの一部である他のビューがあり、最初のビューに戻って、なぜセッションがアクティブでなくなったのか疑問に思っていました。
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
//Setup WCSession
if ([WCSession isSupported]) {
[[WCSession defaultSession] setDelegate:self];
[[WCSession defaultSession] activateSession];
}}
上記は私のためにそれをしました。それが最初にawakeWithContextに配置されていたとしたら、私は愚かです...
このシナリオでは、いくつかの使用例を取り上げます。これらの手順をご覧ください。とても役に立ちます。
1-each device mustに独自のWCSessionインスタンスが構成され、適切なデリゲートが構成されていることを理解します。
2-各デバイスで1つの場所にのみWCSessionDelegateを実装します。 AppDelegateのiOSアプリ、ExtensionDelegateのwatchOS。適切なWCSessionがwatchOSで構成されているが、iPhoneでは2つの異なる場所(ej。アプリのデリゲートで、次にアプリの最初のviewcontorllweerで(私の場合)、動作が不安定になり、時計からメッセージを受信したときにiOSアプリが応答を停止することがある主な理由です。
3-セッションの再アクティブ化は、ホストアプリでのみ行うことをお勧めします。これは、WCSessionDelegateが1つだけのiOSアプリの例です。 (AppDelegate)
#pragma mark - WCSessionDelegate
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error{
if( activationState == WCSessionActivationStateActivated) {
NSLog(@"iPhone WKit session Activated");
}else if (activationState == WCSessionActivationStateInactive) {
NSLog(@"iPhone WKit Inactive");
}else if (activationState == WCSessionActivationStateNotActivated) {
NSLog(@"iPhone WKit NotActivated");
}
}
- (void)sessionDidBecomeInactive:(WCSession *)session{
/*
The session calls this method when it detects that the user has switched to a different Apple Watch. While in the inactive state, the session delivers any pending data to your delegate object and prevents you from initiating any new data transfers. After the last transfer finishes, the session moves to the deactivated state
*/
NSLog(@"sessionDidBecomeInactive");
if (session.hasContentPending) {
NSLog(@"inactive w/ pending content");
}
}
- (void)sessionDidDeactivate:(WCSession *)session{
// Begin the activation process for the new Apple Watch.
[[WCSession defaultSession] activateSession];
//perform any final cleanup tasks related to closing out the previous session.
}
- (void)sessionReachabilityDidChange:(WCSession *)session{
NSLog(@"sessionReachabilityDidChange");
}
最後に、適切なメソッドシグネチャを記述します。watchからデータを送信する返信が必要な場合は、返信のあるメソッドシグネチャを取得します。..Apple以下のメソッドによると、
sendMessage:replyHandler:errorHandler:, sendMessageData:replyHandler:errorHandler:, and transferCurrentComplicationUserInfo:
優先度が高く、すぐに送信されます。アプリが受信したすべてのメッセージは、バックグラウンドスレッドでシリアルにセッションデリゲートに配信されます。
そのため、iOS appDelegateのmainQueueで応答オブジェクトをディスパッチする時間を無駄にしないでください。watchOSで応答が返されるまで待ち、それをメインスレッドに変更して、それに応じてUIを更新します。
Swift 3で、私はこのシグネチャでdidReceiveMessage
の実装を解決しました:
func session(_ session: WCSession, didReceiveMessage message: [String : Any],
replyHandler: @escaping ([String : Any]) -> Void)
WCSession
デリゲートが次のメソッドを実装したことを(チェックして)実装する必要がある場合があります。実装がないためにこのエラーが発生しました。
- (void)session:(WCSession * _Nonnull)session
didReceiveMessage:(NSDictionary<NSString *, id> * _Nonnull)replyMessage
replyHandler:(void (^ _Nonnull)(NSDictionary<NSString *, id> * _Nonnull replyMessage))replyHandler
{
NSLog(@"Received. %@", replyMessage);
[self processResponse:replyMessage];
}