2015-10-14 2 views
12

Dies wird sowohl auf dem Simulator als auch auf dem physischen Gerät iphone5s getestet. Ich habe versucht, WCSession sendMessage zu verwenden, um von der WatchOS2-Erweiterung zu iPhone iOS9-Code zu kommunizieren. Es funktioniert gut, wenn iPhone App im Vordergrund- und Hintergrundmodus läuft.WatchOS2 WCSession sendMessage weckt das iPhone nicht im Hintergrund

Aber wenn ich den iPhone-App (nicht aktiv App überhaupt) zu töten, dann habe ich immer errorhandler Timeout. So kann Watch nicht mehr mit dem iPhone kommunizieren.

"Fehler Domain = WCErrorDomain-Code = 7012 "nahm Nachricht Antwort zu lang." Userinfo = {NSLocalizedDescription = Nachricht Antwort zu lange dauerte., NSLocalizedFailureReason = aufgetreten Reply Timeout.}".

Ich denke, es sollte iPhone App im Hintergrund zu wecken.

Jede Idee, was, um dieses Problem zu arbeiten oder es beheben? Vielen Dank!

+0

Wo auf der iOS-App-Code setzen Sie WCSession up? – ccjensen

+0

Ich habe in den AppDelegate -init-Methoden eingerichtet. Ich dachte, es sollte entweder ViewDidLoad in UIViewController oder Init richtig sein? –

+0

Ja, das klingt gut. Ich nehme an, die Nachricht, die Sie senden, ist ziemlich klein? Wenn ja, schwer zu sagen, was ohne den ganzen Code falsch sein könnte. Vielleicht einen Fehler bei Apple einreichen? – ccjensen

Antwort

9

Nach stundenlangem Versuch und Hinweis von @jeron. Ich habe das Problem selbst herausgefunden.

In meiner Sitzung: didReceiveMessage Delegate-Methode, habe ich zwei Anrufe. 1.replyHandler Anruf. 2. Ich habe einen Async-Prozess ausgeführt (RXPromise) in meinem Fall, verschachtelte es einige RXPromise-Rückrufe, um verschiedene Daten vom Cloud-Dienst zu holen. Ich habe es nicht beachtet, weil es sofort anrufen und zurückkommen soll. Aber jetzt, wo ich RXPromise alle zusammen blockiere, kann es die iOS App jedes Mal im Hintergrund aufwecken.

Endlich finde ich heraus, dass das Problem besteht, weil es nach RXPromise-Aufruf keine Garantie mehr gibt, wieder im Haupt-Thread gelandet zu sein. Und ich glaube Sitzung: didReceiveMessage muss auf dem Hauptthread zurückgegeben werden. Ich habe das nirgendwo in der Apple-Dokumentation erwähnt.

Endlösung:

- (void)session:(WCSession *)session 
    didReceiveMessage:(NSDictionary<NSString *, id> *)message 
     replyHandler:(void (^)(NSDictionary<NSString *, id> *_Nonnull))replyHandler { 

    replyHandler(@{ @"schedule" : @"OK" }); 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     Nested RXPromise calls..... 
    }); 

} 
+0

Hallo @Frank, es ist nicht völlig richtig, dass die Dokumentation nicht das Threading Problem erwähnt. Der Headerkommentar für WCSessionDelegate gibt an, dass die Delegatenrückrufe in einer seriellen Hintergrundwarteschlange auftreten. Aus diesem Grund müssen Sie zu main_queue wechseln, aber alle anderen Informationen sind nützlich und korrekt. – BootMaker

+0

@BootMaker oh danke für die Info. Ich werde mal schauen. –

0

Nun, Sie können transferUserInfo verwenden, um die Anrufe in die Warteschlange zu stellen. Die Verwendung von sendMessage führt zu Fehlern, wenn die App beendet wird.

+0

Mein Ziel ist eigentlich, Daten aus der Cloud über das iPhone zu holen. Aus verschiedenen Gründen kann ich NSUrlSession nicht direkt von WatchExtension aus verwenden. Daher möchte ich, dass sendmessage das iPhone aufweckt, Daten aus der Cloud anfordert und dann transferUserInfo verwendet, um die Daten an die Watch zurückzusenden. –

13

Es ist wichtig, dass Sie die in Ihrer AppDelegatedidFinishLaunchingWithOptions Methode aktivieren. Außerdem müssen Sie die WCSessionDelegate dort einstellen. Wenn Sie es anderswo machen, wird der Code möglicherweise nicht ausgeführt, wenn das System die getötete App im Hintergrund startet.

Auch sollte man die Antwort über die replyHandler senden. Wenn Sie versuchen, etwas anderes zu senden, wartet das System auf eine Antwort, die nie kommt. Daher der Zeitüberschreitungsfehler. Hier

ist ein Beispiel, das das App aufwacht, wenn er getötet wird:

Im WatchExtension:

Einrichten der Sitzung. Typischerweise in Ihrem ExtensionDelegate:

func applicationDidFinishLaunching() { 
    if WCSession.isSupported() { 
     let session = WCSession.defaultSession() 
     session.delegate = self 
     session.activateSession() 
    } 
} 

Und dann die Nachricht senden, wenn Sie etwas aus der App benötigen:

if WCSession.defaultSession().reachable { 
    let messageDict = ["message": "hello iPhone!"] 
    WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in 
     print(replyDict) 
     }, errorHandler: { (error) -> Void in 
     print(error) 
    } 
} 

In der iPhone App:

Gleiche Einrichtung einer Sitzung, aber dieses Mal Stellen Sie auch den Delegierten ein:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 
    ... 
    if WCSession.isSupported() { 
     let session = WCSession.defaultSession() 
     session.delegate = self 
     session.activateSession() 
    } 
} 

Und dann implementieren die Delegatmethode die Antwort auf die Uhr senden:

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) { 
    replyHandler(["message": "Hello Watch!"]) 
} 

Dies funktioniert, wenn es eine Verbindung zwischen der Uhr und dem iPhone. Wenn die App nicht läuft, startet das System sie im Hintergrund.

Ich weiß nicht, ob das System lange genug wartet, bis Sie Ihre Daten von iCloud empfangen, aber dieses Beispiel auf jeden Fall die App aufwacht.

+0

Danke Joern, ich habe genau deinen Weg auch oben versucht. Es startet die iOS-App immer noch nicht im Hintergrund. Sobald ich die iOS-App öffne, fängt alles wieder an zu arbeiten. Hast du versucht, deine iOS-App zu beenden und die Uhr zu testen, um zu sehen, ob sie funktioniert? –

+1

Ja, ich habe das versucht und bekomme eine Antwort, auch wenn die App getötet wurde. – joern

+0

Haben Sie es versucht, indem Sie einen kurzen Text gesendet oder etwas von iCloud heruntergeladen haben? – joern