iPhone 和 Apple Watch 之间的 WatchConnectivity 超时错误

Posted

技术标签:

【中文标题】iPhone 和 Apple Watch 之间的 WatchConnectivity 超时错误【英文标题】:WatchConnectivity timeout error between iPhone and Apple Watch 【发布时间】:2017-09-11 06:14:53 【问题描述】:

我正在尝试使用 WatchConnectivity 框架将字符串从我的 Apple Watch 发送到我的 iPhone。

我已经开始了两个会话,一个 WCSession 和一个 HKSession,因为我想在读取时将心率信息从手表传输到 iPhone。 (如果有更好的方法请告诉我)。

如果有帮助,我已经发布了我收到的日志消息。

2017-04-14 20:01:24.660433-0400 MoodTunes WatchKit Extension[180:16033] [WC] -[WCSession _onqueue_notifyOfMessageError:messageID:withErrorHandler:] AD8F92C9-FCAA-45B0-9B4C-0D5C95B72BEE
 errorHandler: YES with WCErrorCodeTransferTimedOut->IDSErrorTypeTimedOut-
>IDSResponseTimedOut
Watch send failed with error Error Domain=WCErrorDomain Code=7017 "Transfer 
timed out." UserInfo=NSUnderlyingError=0x175a7da0 Error 
Domain=com.apple.identityservices.error Code=23 "Timed out" UserInfo=
NSUnderlyingError=0x1752d4f0 Error Domain=com.apple.ids.idssenderrordomain 
Code=12 "(null)", NSLocalizedDescription=Timed out, 
NSLocalizedDescription=Transfer timed out. 

iPhone ViewController.swift

import HealthKit
import WatchKit
import Foundation
import CoreLocation
import WatchConnectivity
import UIKit

class ViewController: UIViewController, CLLocationManagerDelegate, 
WCSessionDelegate  

@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var milesLabel: UILabel!
@IBOutlet weak var hrLabel: UILabel!
let session = WCSession.default()
static let sharedManager = ViewController()
let healthManager:HealthKitManager = HealthKitManager()
let healthStore = HKHealthStore()
let heartRateUnit = HKUnit(from: "count/min")

//var anchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
var currenQuery : HKQuery?    // the current query can be nil
var heartRate: HKQuantitySample?
var zeroTime = TimeInterval()
var counter =  0



override func viewWillAppear(_ animated: Bool) 
    if (WCSession.isSupported()) 
        session.delegate = self
        session.activate()
    
    getHealthKitPermission()

override func viewDidLoad() 
    super.viewDidLoad()
    if (WCSession.isSupported()) 
        session.delegate = self
        session.activate()
    
    getHealthKitPermission()

override func didReceiveMemoryWarning() 
    super.didReceiveMemoryWarning()

// Function to get access to the user's Healthkit
func getHealthKitPermission() 
    // checks if the health data is avaiable
    if  HKHealthStore.isHealthDataAvailable() == true hrLabel.text = "Good" else 
        hrLabel.text = "not available"
        return
    

    guard let quantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else 
      displayNotAllowed()
        return
    
    let dataTypes = Set(arrayLiteral: quantityType)
    healthStore.requestAuthorization(toShare: dataTypes, read: dataTypes)  (success, error) -> Void in
        if success == false 
            self.displayNotAllowed()
        
    

func displayNotAllowed() 
    hrLabel.text = "not allowed"


// function crates a heartrate query for the healthstore. it returns an optional HKQuery
func createHeartRateStreamingQuery(_ workoutStartDate: Date) -> HKQuery? 


    guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else  return nil 
    let datePredicate = HKQuery.predicateForSamples(withStart: workoutStartDate, end: nil, options: .strictEndDate )

    //let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
    let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate])


    let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit))  (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
        //guard let newAnchor = newAnchor else return
        //self.anchor = newAnchor
        self.updateHeartRate(sampleObjects)
    

    heartRateQuery.updateHandler = (query, samples, deleteObjects, newAnchor, error) -> Void in
        //self.anchor = newAnchor!
        self.updateHeartRate(samples)
    
    return heartRateQuery

// updates the display on the UI, heartRateLabel and calls animateHeart for the heart enlargment
func updateHeartRate(_ samples: [HKSample]?) 
    guard let heartRateSamples = samples as? [HKQuantitySample] else return
    var value = 0.0
    DispatchQueue.main.async 
        guard let sample = heartRateSamples.first elsereturn
        value = sample.quantity.doubleValue(for: self.heartRateUnit)
        self.hrLabel.text = "Hello"
        //self.hrLabel.text = String(UInt16(value))

    


        // retrieve source from sample
@available(ios 9.3, *)
func session(_: WCSession, activationDidCompleteWith: WCSessionActivationState, error: Error?) 



func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) 
    DispatchQueue.main.async() 
        guard let m = message["m"] as? String else  return 
      //  self.label.text = m
    

func sessionDidBecomeInactive(_ sesson: WCSession) -> Void 


func sessionDidDeactivate (_ session: WCSession) -> Void 


@IBAction func shareData(_ sender: Any) 
    if let query = createHeartRateStreamingQuery(Date()) 
        self.currenQuery = query
        healthStore.execute(query)






这是我的 Apple Watch 应用的 interfaceController

InterfaceController.swift

import WatchKit
import Foundation
import UIKit
import CoreLocation
import HealthKit
import WatchConnectivity

class InterfaceController: WKInterfaceController, HKWorkoutSessionDelegate, 
WCSessionDelegate 
@IBOutlet private weak var heartRateLabel: WKInterfaceLabel!
@IBOutlet private weak var heart: WKInterfaceImage!
@IBOutlet private weak var startStopButton : WKInterfaceButton!
@IBOutlet private weak var label : WKInterfaceLabel!

@IBOutlet var dataLabel: WKInterfaceLabel!
@IBOutlet private weak var deviceLabel: WKInterfaceLabel!
let healthStore = HKHealthStore() // Creates an instance of the healthkit store

//State of the app - is the workout activated
var workoutActive = false

// define the activity type and location
var hkSession : HKWorkoutSession?  // ? means the session can be nil
var session = WCSession.default()
let heartRateUnit = HKUnit(from: "count/min")

//var anchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
var currenQuery : HKQuery?    // the current query can be nil

override func awake(withContext context: Any?) 
    super.awake(withContext: context)
    if WCSession.isSupported() 
        session.delegate = self
        session.activate()
    


override func willActivate() 
    super.willActivate()

    // checks if the health data is avaiable
    guard HKHealthStore.isHealthDataAvailable() == true else 
        heartRateLabel.setText("not available")
        return
    

    guard let quantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else 
        displayNotAllowed()
        return
    
    let dataTypes = Set(arrayLiteral: quantityType)
    healthStore.requestAuthorization(toShare: dataTypes, read: dataTypes)  (success, error) -> Void in
        if success == false 
            self.displayNotAllowed()
        
    


func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) 
    DispatchQueue.main.async() 
        guard let m = message["m"] as? String else  return 
        self.dataLabel.setText(m)
    


@available(watchOSApplicationExtension 2.2, *)
func session(_: WCSession, activationDidCompleteWith: WCSessionActivationState, error: Error?) 



func displayNotAllowed() 
    heartRateLabel.setText("not allowed")

// Function used to set the state of the workout, if it's still "running" then call workoutDidStart else call workoutDidEnd
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) 
    switch toState 
    case .running:
        workoutDidStart(date)
    case .ended:
        workoutDidEnd(date)
    default:
        print("Unexpected state \(toState)")
    


func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) 
    // Do nothing for now
    print("Workout error")


// function used to query the healthkitstore when the workout is active
func workoutDidStart(_ date : Date) 
    if let query = createHeartRateStreamingQuery(date) 
        self.currenQuery = query
        healthStore.execute(query)
     else 
        heartRateLabel.setText("cannot start")
    


func workoutDidEnd(_ date : Date) 
    healthStore.stop(self.currenQuery!)
    heartRateLabel.setText("---")
    hkSession = nil


// MARK: - Actions
@IBAction func startBtnTapped() 
    if (self.workoutActive) 
        //finish the current workout
        self.workoutActive = false
        self.startStopButton.setTitle("Start")
        if let workout = self.hkSession 
            healthStore.end(workout)
        
     else 
        //start a new workout
        self.workoutActive = true
        self.startStopButton.setTitle("Stop")
        startWorkout()
    



func startWorkout() 

    // If we have already started the workout, then do nothing.
    if (hkSession != nil) 
        return
    

    // Configure the workout session.
    let workoutConfiguration = HKWorkoutConfiguration()
    workoutConfiguration.activityType = .crossTraining
    workoutConfiguration.locationType = .indoor

    do 
        hkSession = try HKWorkoutSession(configuration: workoutConfiguration)
        hkSession?.delegate = self
     catch 
        fatalError("Unable to create the workout session!")
    

    healthStore.start(self.hkSession!)


// function crates a heartrate query for the healthstore. it returns an optional HKQuery
func createHeartRateStreamingQuery(_ workoutStartDate: Date) -> HKQuery? 


    guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else  return nil 
    let datePredicate = HKQuery.predicateForSamples(withStart: workoutStartDate, end: nil, options: .strictEndDate )

    //let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
    let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate])


    let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit))  (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
        //guard let newAnchor = newAnchor else return
        //self.anchor = newAnchor
        self.updateHeartRate(sampleObjects)
    

    heartRateQuery.updateHandler = (query, samples, deleteObjects, newAnchor, error) -> Void in
        //self.anchor = newAnchor!
        self.updateHeartRate(samples)
    
    return heartRateQuery

// updates the display on the UI, heartRateLabel and calls animateHeart for the heart enlargment
func updateHeartRate(_ samples: [HKSample]?) 
    guard let heartRateSamples = samples as? [HKQuantitySample] else return
    var value = 0.0
    DispatchQueue.main.async 
        guard let sample = heartRateSamples.first elsereturn
        value = sample.quantity.doubleValue(for: self.heartRateUnit)
        self.heartRateLabel.setText(String(UInt16(value)))

        // retrieve source from sample
        let name = sample.sourceRevision.source.name
        self.updateDeviceName(name)
        self.animateHeart()
    
    if (self.session.isReachable) 
        self.sendMessage(value: value)
    



// function to create and send the message the iphone
func sendMessage(value: Double) 
    let strValue = String(UInt16(value))
    let message = [ "m": strValue ]
    self.session.sendMessage(message, replyHandler:nil, errorHandler:  (error) -> Void in
        print("Watch send failed with error \(error)")
    )

func updateDeviceName(_ deviceName: String) 
    deviceLabel.setText(deviceName)



func animateHeart() 
    self.animate(withDuration: 0.5) 
        self.heart.setWidth(60)
        self.heart.setHeight(90)
    

    let when = DispatchTime.now() + Double(Int64(0.5 * double_t(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)

    DispatchQueue.global(qos: .default).async 
        DispatchQueue.main.asyncAfter(deadline: when) 
            self.animate(withDuration: 0.5, animations: 
                self.heart.setWidth(50)
                self.heart.setHeight(80)
            )
        
    

【问题讨论】:

【参考方案1】:

WCErrorCodeTransferTimedOut->IDSErrorTypeTimedOut->IDSResponseTimedOut

我通过重启 iphone 和 watch 解决了这个错误。

【讨论】:

以上是关于iPhone 和 Apple Watch 之间的 WatchConnectivity 超时错误的主要内容,如果未能解决你的问题,请参考以下文章

iPhone 和 Apple Watch 之间的 WatchConnectivity 超时错误

Watch OS 2:是不是可以获得配对的 iPhone 和 Apple Watch 之间连接的蓝牙信号强度?

有没有办法确定 Apple Watch 和 iPhone 之间的粗略距离?

如何在 iPhone 应用程序和 Apple Watch 之间共享数据?

iPhone 和 Apple Watch 应用之间的 Core Data SQLite 存储更改通知

在 Apple Watch 上显示 iPhone 的电池