ios - "La respuesta del mensaje tomó demasiado tiempo."-Observe los problemas de conectividad con Watch OS 3
swift watch-os-3 (2)
En mi proyecto, utilizo Watch Connectivity
para enviar mensajes ay desde Watch y iPhone. Puedo enviar un mensaje al teléfono y recibir una serie de cadenas al iniciar la aplicación; sin embargo, al usar acciones obtengo el siguiente error;
Error Domain = WCErrorDomain Code = 7012 "La respuesta del mensaje tomó demasiado tiempo".
Así es como se configuran las cosas;
Primero, el reloj envía un mensaje al teléfono y luego el teléfono envía una matriz de cadenas para mostrar en una WKInterfaceTable
. Esto a veces funciona al cargar la aplicación. (Busco todos los elementos denominados NSManagedObjects y uso sus propiedades de cadena de title
para almacenarlos en una array
llamada watchItems
.
Sin embargo, tengo una acción en el reloj para eliminar todos los elementos en la matriz y actualizar la tabla con los datos nuevos.
La acción en el reloj utiliza una función sendMessage
para enviar el item
al teléfono para eliminarlo de la matriz, luego el teléfono envía la matriz recién actualizada al reloj y el reloj actualiza la tabla. Sin embargo, devuelvo la misma matriz o un error.
Bastante simple, así que todo funcionó bien antes de Swift 3 y Watch OS3 / iOS 10; toda la aplicación solía funcionar.
Así es como tengo todo configurado;
Delegado de la aplicación telefónica
import WatchConnectivity
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var session : WCSession!
var items = [Items]()
func loadData() {
let moc = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
let request = NSFetchRequest<Items>(entityName: "Items")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
request.predicate = NSPredicate(format: "remove == 0", "remove")
do {
try
self.items = moc!.fetch(request)
// success ...
} catch {
// failure
print("Fetch failed")
}
}
//WATCH EXTENSION FUNCTIONS
//IOS 9.3
/** Called when the session has completed activation. If session state is WCSessionActivationStateNotActivated there will be an error with more details. */
//HAVE TO INCLUDE
@available(iOS 9.3, *)
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?){
print("iPhone WCSession activation did complete")
}
@available(iOS 9.3, *)
func sessionDidDeactivate(_ session: WCSession) {}
func sessionWatchStateDidChange(_ session: WCSession) {}
func sessionDidBecomeInactive(_ session: WCSession) {
}
//APP DELEGATE FUNCTIONS
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.default()
session.delegate = self;
session.activate()
}
return true
}
}
//DID RECIEVE MESSAGE
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Swift.Void) {
loadData()
func loadItems() {
watchItems.removeAll()
for a in self.items {
watchItems.append(a.title)
}
}
var watchItems = ["1","2","3","4","5"]
let value = message["Value"] as? String
//This is called when user loads app, and takes some time when using refresh action, sometimes times out
if value == "HELLOiPhone/+@=" {
print("Hello Message Recieved")
loadItems()
//send a reply
replyHandler( [ "Items" : Items ] )
}
//Not sure if receiving but does not delete array and send back to watch
if value == "removeALL@+=-/" {
for index in self.items {
index.remove = 1
//Saves MOC
}
loadData()
loadTasksData()
//send a reply
replyHandler( [ "Items" : Items ] )
}
else {
for index in self.items {
if index.title == value {
index.remove = 1
//Saves MOC
}
}
loadData()
loadTasksData()
//send a reply
replyHandler( [ "Items" : Items ] )
}
}
RELOJ
import WatchConnectivity
class SimplelistInterfaceController: WKInterfaceController, WCSessionDelegate {
/** Called when the session has completed activation. If session state is WCSessionActivationStateNotActivated there will be an error with more details. */
@available(watchOS 2.2, *)
public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
//Fetch data is a function which sends a "HELLOiPhone/+@=" message to receive the array and displays in the table. This works
fetchData()
}
var session : WCSession!
var items = ["Refresh Items"]
override func didAppear() {
fetchData()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.default()
session.delegate = self
session.activate()
}
fetchData()
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
fetchData()
}
@IBAction func refresh() {
print("Refresh")
//Works but sometimes message is delayed
fetchData()
}
@IBAction func removeAll() {
print("Remove All Items is called")
if WCSession.default().isReachable {
let messageToSend = ["Value":"removeALL@+=-/"]
print("/(messageToSend)")
session.sendMessage(messageToSend, replyHandler: { replyMessage in
if let value = replyMessage["Items"] {
self.items = value as! [String]
Not receiving message
print("Did Recieve Message, items = /(self.items)")
}
}, errorHandler: {error in
// catch any errors here
print(error)
})
}
fetchData()
}
}
No debe enviar ni recibir objetos de clase personalizados desde un destino (iOS) al segundo objetivo (watchOS), sino que debe enviar / recibir datos en formato de diccionario como [String: Any] y este diccionario debe contener una matriz de sus propiedades personalizadas requeridas. en el par de valores clave en el diccionario simple. Esto podría ser fácilmente decodificable en el lado del reloj.
Debería crear una clase desacoplada que extienda WCSessionDelegate tal como se muestra a continuación, para que esta clase se pueda usar no solo en ExtensionDelegate sino también en cualquier WKInterfaceController.
class WatchSessionManager: NSObject, WCSessionDelegate { static let sharedManager = WatchSessionManager() private override init() { super.init() self.startSession() } private let session: WCSession = WCSession.default func startSession() { session.delegate = self session.activate() } func tryWatchSendMessage(message: [String: Any], completion: (([String: Any]) -> Void)? = nil) { print("tryWatch /(message)") weak var weakSelf = self if #available(iOS 9.3, *) { if weakSelf?.session.activationState == .activated { if weakSelf?.session.isReachable == true { weakSelf?.session.sendMessage(message, replyHandler: { [weak self] ( response ) in guard let slf = self else {return} //Get the objects from response dictionary completion?(response) }, errorHandler: { [weak self] ( error ) in guard let slf = self else {return} print ( "Error sending message: % @ " , error ) // If the message failed to send, queue it up for future transfer slf.session.transferUserInfo(message) }) } else { self.session.transferUserInfo(message) } }else{ self.session.activate() self.session.transferUserInfo(message) } } else { // Fallback on earlier versions if self.session.activationState == .activated { if self.session.isReachable == true { self.session.sendMessage(message, replyHandler: { ( response ) in //Get the objects from response dictionary completion?(response) }, errorHandler: { ( error ) in print ( "Error sending message: % @ " , error ) // If the message failed to send, queue it up for future transfer self.session.transferUserInfo(message) }) } else { self.session.transferUserInfo(message) } }else{ self.session.activate() self.session.transferUserInfo(message) } } } }
Ahora podría enviar fácilmente un mensaje a su aplicación iOS para despertar y obtener datos de allí (por ejemplo, desde CoreData) usando la función anterior en cualquier WKInterfaceController y el bloque de finalización tendrá sus datos requeridos, tales como
let dict: [String: Any] = ["request": "FirstLoad"]
WatchSessionManager.sharedManager.tryWatchSendMessage(message: dict,completion:{ (data) in print(data)})
De la misma manera que debería utilizar este WatchSessionManager en iOS y recibir la solicitud y según la clave solicitada, debe tomar datos del núcleo de almacenamiento / base de datos y enviar la lista de objetos personalizados en un patrón simple de valor clave dentro de la función replyHandler of didreceiveMessage como se muestra a continuación .
func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
var dict: [String: Any] = [String: Any]()
replyHandler(dict) //This dict will contain your resultant array to be sent to watchApp.
}
En algún momento, la aplicación iOS (estado asesinado) no es alcanzable para WatchApp, para resolver ese problema debe llamar a "tryWatchSendMessage" dentro del temporizador de aproximadamente 3 segundos de intervalo. Y cuando obtenga la conexión de watchApp, debe invalidar el temporizador.
La funcionalidad sendMessage de WatchConnectivity es tan poderosa para activar su aplicación. Debe usarlo de manera optimizada.
Acabo de tratar con mi aplicación WatchOS. Y hubo una situación en la que recibí "La respuesta del mensaje tomó demasiado tiempo".
Luego agregué el controlador de tareas en segundo plano a mi aplicación iOS y comencé a enviar mensajes cada segundo a la aplicación WatchOS. El mensaje contenía UIApplication.shared.backgroundTimeRemaining
. Entonces obtengo: 45sec, 44sec, ..., 6sec, 5sec, ... Si el temporizador se ejecuta por debajo de 5 segundos, no se enviarán mensajes a / desde la aplicación de iOS y obtendremos "La respuesta del mensaje demoró demasiado". La solución más fácil fue enviar mensajes en blanco de reloj a teléfono cada vez que el temporizador pasa a menos de 15 segundos. El backgroundTimeRemaining
se actualizará nuevamente a 45 segundos: 45, 44, 43, ..., 17, 16, 15, (mensaje en blanco), 45, 44, 43, ...
Espero que ayude a alguien