WCSession 的 transferUserInfo 在 iOS 9.3 的 watchOS 2.2 中不再可靠地工作

WCSession's transferUserInfo no longer reliably working in watchOS 2.2 with iOS 9.3

我有一个现有的 iOS 9.2 和 watchOS 2.1 应用程序,它使用 sendMessagetransferUserInfo 将数据从 iPhone 发送到 Apple Watch。如果 sendMessage 失败,我正在使用 transferUserInfo 将数据排队等待稍后交付:

// *** In the iOS app ***
self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
    // If the message failed to send, queue it up for future transfer
    self.session.transferUserInfo(message)
}

// *** In the watchOS app ***
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
    // Handle message here
}

func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
    // Handle message here
}

在不更改任何代码的情况下,运行 iOS 9.3 上的应用程序在真实设备上使用 watchOS 2.2(模拟器没有相同的问题),sendMessage 将数据传送到Apple Watch 只要手表在范围内并且屏幕打开。这是预期的,也是它以前的工作方式。但是,如果屏幕关闭并且 sendMessage 失败,当屏幕重新打开时 transferUserInfo 不再向 Apple Watch 传输数据。

为了找出出错的地方,我添加了以下 WCSessionDelegate 方法来查看 iOS 应用程序是否无法发送数据:

func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
    // Called when self.session.transferUserInfo completes
}

调用 transferUserInfo 后确实会调用此方法,但没有返回任何错误,iOS 应用程序似乎表明传输成功。

一开始我以为可能传输数据的时间变长了,但是放了一天数据还是没有传输。我现在有点怀疑它与新的多手表 API 有关,也许 iOS 应用程序需要知道一个特定的手表才能将其发送到,尽管我只有一个手表配对。有没有人对可能发生的变化以及如何正确使用 transferUserInfo 有任何想法?

我想我现在可以正常工作了。首先,我必须将新的 WCSessionDelegate 方法添加到我的 iOS app:

@available(iOS 9.3, *)
func session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) {
    if activationState == WCSessionActivationState.Activated {
        NSLog("Activated")
    }

    if activationState == WCSessionActivationState.Inactive {
        NSLog("Inactive")
    }

    if activationState == WCSessionActivationState.NotActivated {
        NSLog("NotActivated")
    }
}

func sessionDidBecomeInactive(session: WCSession) {
    NSLog("sessionDidBecomeInactive")
}

func sessionDidDeactivate(session: WCSession) {
    NSLog("sessionDidDeactivate")

    // Begin the activation process for the new Apple Watch.
    self.session.activateSession()
}

与我的手表OS应用类似:

@available(watchOSApplicationExtension 2.2, *)
func session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) {
    if activationState == WCSessionActivationState.Activated {
        NSLog("Activated")
    }

    if activationState == WCSessionActivationState.Inactive {
        NSLog("Inactive")
    }

    if activationState == WCSessionActivationState.NotActivated {
        NSLog("NotActivated")
    }
}

但是 transferUserInfo 仍然对我不起作用,特别是当 Apple Watch 的屏幕关闭时。下面是我在 iPhone 和 Apple Watch 之间发送信息的方式 iOS 9.2/watchOS 2.1:

func tryWatchSendMessage(message: [String : AnyObject]) {
    if self.session != nil && self.session.paired && self.session.watchAppInstalled {
        self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
            // If the message failed to send, queue it up for future transfer
            self.session.transferUserInfo(message)
        }
    }
}

我假设在手表屏幕关闭时从 iPhone 向 Apple Watch 发送消息会导致 transferUserInfo 失败,因为它在 sendMessage 的错误处理程序中。当屏幕打开时,sendMessage 也按预期工作。但是,如果您的手表屏幕关闭,看起来 sendMessage 的错误回复处理程序并不总是被调用,即使请求失败也是如此。这与以前的 OS 版本不同。这似乎也造成了级联效应,即即使条件合适,后续消息也会失败。这就是让我相信 transferUserInfo 是罪魁祸首的原因。

我发现为了让我的消息可靠地传递,我需要同时检查 reachable 和 activationState。由于我还想继续支持早期的 iOS 和 watchOS 版本,因此我的 tryWatchSendMessage 方法变成了以下内容:

func tryWatchSendMessage(message: [String : AnyObject]) {
    if #available(iOS 9.3, *) {
        if self.session != nil && self.session.paired && self.session.watchAppInstalled && self.session.activationState == .Activated {
            if self.session.reachable == true {
                self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
                    // If the message failed to send, queue it up for future transfer
                    self.session.transferUserInfo(message)
                }
            } else {
                self.session.transferUserInfo(message)
            }
        }
    } else {
        // Fallback on earlier versions
        if self.session != nil && self.session.paired && self.session.watchAppInstalled {
            if self.session.reachable == true {
                self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
                    // If the message failed to send, queue it up for future transfer
                    self.session.transferUserInfo(message)
                }
            } else {
                self.session.transferUserInfo(message)
            }
        }
    }
}

进行这些更改似乎解决了我遇到的问题。我很想知道这些是否有助于解决其他人的问题,或者是否仍然存在与 transferUserInfo 不工作相关的问题。

自上周 2.2 更新以来,我在让通信正常工作方面遇到了非常相似的问题 - 但对我来说,我无法让应用程序上下文或文件传输过来。

和其他人一样,它在模拟器中有效,但在设备上无效。

我今天注意到我试图从后台线程发送数据 - 我将所有调用都包装在一个 dispatch_async(dispatch_get_main_queue()) 中,突然间一切正常了。