如何诊断和解决 WCSession sendMessage(_:replyHandler:errorHandler:) 上的崩溃?

How can I diagnose and resolve a crash on WCSession sendMessage(_:replyHandler:errorHandler:)?

我正在构建一个 watchOS 应用程序,它需要定期从配套 iPhone 应用程序请求信息以刷新复杂功能。

为此,我有一个定期运行的 WKApplicationRefreshBackgroundTask。它使用 WatchConnectivity 中的 sendMessage(_:replyHandler:errorHandler:) 从 iPhone 应用请求信息,处理回复,并更新并发症。

大多数情况下,这对许多用户来说都是可靠的,但是,我看到 Apple Watch 在与 sendMessage(_:replyHandler:errorHandler:) 相关的 Xcode 中崩溃,我担心这会导致用户错过并发症更新.一些用户一直在抱怨复杂功能没有更新,所以我试图确定是否存在超出 watchOS 中复杂功能刷新频率的常规限制的问题。

有人对我如何解决这个问题有什么建议吗?或者,你对我如何更好地解决导致崩溃和图形的问题有什么建议吗?如何预防呢?

我在底部包含了一个崩溃的完整示例,以及处理后台任务的代码和处理从成对的 iPhone 发送和接收值的代码。

我从不在没有 replyHandler 的情况下发送消息,所以虽然 others have seen problems because the delegate method for a message without a handler was not implemented 在 iPhone 上,但我认为这不是这里的问题。为了安全起见,我将 iPhone 上的委托方法实现为一个什么都不做的空方法。

2020 年 1 月 30 日更新: 一位朋友建议,问题可能是任务仍在进行中时被 10 秒计时器标记为完成,从而导致内存问题当未决的事情完成时,但不确定可以做些什么。也许这就是这里问题的核心?

这是我来自 ExtensionDelegate 的后台刷新代码:

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for task in backgroundTasks {
        switch task {
        case let backgroundTask as WKApplicationRefreshBackgroundTask:

            // set a timer in case it doesn't complete
            // the maximum allowed is 15 seconds, and then it crashes, so schedule the new task and mark complete after 10
            var timeoutTimer: Timer? = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { (timer) in

                self.scheduleBackgroundRefresh()

                backgroundTask.setTaskCompletedWithSnapshot(false)
            }

            // schedule the next refresh now in case the request crashes
            scheduleBackgroundRefresh()

            WatchConnectivityManager.shared.requestDataFromPhone()

            ComplicationManager.shared.reloadComplication()

            // as long as the expiration timer is valid, cancel the timer and set the task complete
            // otherwise, we'll assume the timer has fired and the task has been marked complete already
            // if it's marked complete again, that's a crash
            if let timerValid = timeoutTimer?.isValid, timerValid == true {
                timeoutTimer?.invalidate()
                timeoutTimer = nil
                backgroundTask.setTaskCompletedWithSnapshot(true)
            }

        default:
            // make sure to complete unhandled task types
            task.setTaskCompletedWithSnapshot(false)
        }
    }
}

private func scheduleBackgroundRefresh() {

    let fiveMinutesFromNow: Date = Date(timeIntervalSinceNow: 5 * 60)

    WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fiveMinutesFromNow,
    userInfo: nil) { (error) in
        if let error = error {
            fatalError("\(error)")
        }
    }
}

这里是 WatchConnectivityManager:

import Foundation
import WatchKit
import WatchConnectivity

class WatchConnectivityManager: NSObject {

    static let shared = WatchConnectivityManager()

    let session = WCSession.default

    private let receivedMessageQueue: OperationQueue = {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        return queue
    }()

    private func process(messageOrUserInfo: [String : Any]) {

        receivedMessageQueue.addOperation {
            if let recievedValue = messageOrUserInfo["ValueFromPhone"] as? Int {
                DispatchQueue.main.async {
                    ViewModel.shared.valueFromPhone = recievedValue
                }
            }
        }
    }

    func requestDataFromPhone() {

        if session.activationState == .activated {

            let message: [String : Any] = ["Request" : true]

            let replyHandler: (([String : Any]) -> Void) = { reply in

                self.process(messageOrUserInfo: reply)
            }

            let errorHandler: ((Error) -> Void) = { error in
            }

            if session.isReachable {
                session.sendMessage(message,
                                    replyHandler: replyHandler,
                                    errorHandler: errorHandler)
            }

            // send a request to the iPhone as a UserInfo in case the message fails
            session.transferUserInfo(message)
        }
    }
}

extension WatchConnectivityManager: WCSessionDelegate {

    func session(_ session: WCSession,
                 activationDidCompleteWith activationState: WCSessionActivationState,
                 error: Error?) {

        if activationState == .activated {
            requestDataFromPhone()
        }
    }

    func session(_ session: WCSession,
                 didReceiveUserInfo userInfo: [String : Any])
    {
        process(messageOrUserInfo: userInfo)
    }
}

崩溃示例:

Hardware Model:      Watch3,4
AppVariant:          1:Watch3,4:6
Code Type:           ARM (Native)
Role:                Non UI
Parent Process:      launchd [1]

OS Version:          Watch OS 6.1.1 (17S449)
Release Type:        User
Baseband Version:    n/a

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000004
VM Region Info: 0x4 is not in any region.  Bytes before following region: 638972
      REGION TYPE              START - END     [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                 0009c000-000ac000 [   64K] r-x/r-x SM=COW  ...x/App Name

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [377]
Triggered by Thread:  9

Thread 0 name:
Thread 0:
0   libsystem_kernel.dylib          0x4381f864 semaphore_wait_trap + 8
1   libdispatch.dylib               0x436dac26 _dispatch_sema4_wait + 12 (lock.c:139)
2   libdispatch.dylib               0x436db09a _dispatch_semaphore_wait_slow + 104 (semaphore.c:132)
3   FrontBoardServices              0x46de503e -[FBSSceneSnapshotRequestHandle performRequestForScene:] + 408 (FBSSceneSnapshotRequestHandle.m:67)
4   FrontBoardServices              0x46de96ac -[FBSSceneSnapshotAction snapshotRequest:performWithContext:] + 218 (FBSSceneSnapshotAction.m:168)
5   FrontBoardServices              0x46da4320 -[FBSSceneSnapshotRequest performSnapshotWithContext:] + 292 (FBSSceneSnapshotRequest.m:65)
6   UIKitCore                       0x5ba6a000 __65-[UIApplication _performSnapshotsWithAction:forScene:completion:]_block_invoke_3 + 168 (UIApplication.m:7655)
7   FrontBoardServices              0x46de9568 -[FBSSceneSnapshotAction _executeNextRequest] + 244 (FBSSceneSnapshotAction.m:135)
8   FrontBoardServices              0x46de91e0 -[FBSSceneSnapshotAction executeRequestsWithHandler:completionHandler:expirationHandler:] + 244 (FBSSceneSnapshotAction.m:87)
9   UIKitCore                       0x5ba69f20 __65-[UIApplication _performSnapshotsWithAction:forScene:completion:]_block_invoke_2 + 238 (UIApplication.m:7650)
10  UIKitCore                       0x5ba69696 -[UIApplication _beginSnapshotSessionForScene:withSnapshotBlock:] + 772 (UIApplication.m:7582)
11  UIKitCore                       0x5ba69e16 __65-[UIApplication _performSnapshotsWithAction:forScene:completion:]_block_invoke + 112 (UIApplication.m:7648)
12  UIKitCore                       0x5b2c1110 -[UIScene _enableOverrideSettingsForActions:] + 40 (UIScene.m:1206)
13  UIKitCore                       0x5b2c1330 -[UIScene _performSystemSnapshotWithActions:] + 112 (UIScene.m:1230)
14  UIKitCore                       0x5ba69b90 -[UIApplication _performSnapshotsWithAction:forScene:completion:] + 382 (UIApplication.m:7647)
15  UIKitCore                       0x5be89586 __98-[_UISceneSnapshotBSActionsHandler _respondToActions:forFBSScene:inUIScene:fromTransitionCont... + 146 (_UISceneSnapshotBSActionsHandler.m:54)
16  UIKitCore                       0x5ba68fd4 _runAfterCACommitDeferredBlocks + 274 (UIApplication.m:3038)
17  UIKitCore                       0x5ba5b3da _cleanUpAfterCAFlushAndRunDeferredBlocks + 198 (UIApplication.m:3016)
18  UIKitCore                       0x5ba82702 _afterCACommitHandler + 56 (UIApplication.m:3068)
19  CoreFoundation                  0x43b63644 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 18 (CFRunLoop.c:1758)
20  CoreFoundation                  0x43b5f43c __CFRunLoopDoObservers + 350 (CFRunLoop.c:1868)
21  CoreFoundation                  0x43b5f956 __CFRunLoopRun + 1150 (CFRunLoop.c:2910)
22  CoreFoundation                  0x43b5f23a CFRunLoopRunSpecific + 370 (CFRunLoop.c:3192)
23  GraphicsServices                0x46973cd0 GSEventRunModal + 96 (GSEvent.c:2246)
24  UIKitCore                       0x5ba61580 UIApplicationMain + 1730 (UIApplication.m:4773)
25  libxpc.dylib                    0x438fbcf0 _xpc_objc_main.cold.3 + 152
26  libxpc.dylib                    0x438eca34 _xpc_objc_main + 184 (main.m:126)
27  libxpc.dylib                    0x438ee934 xpc_main + 110 (init.c:1568)
28  Foundation                      0x443f3156 -[NSXPCListener resume] + 172 (NSXPCListener.m:276)
29  PlugInKit                       0x4b58b26c -[PKService run] + 384 (PKService.m:165)
30  WatchKit                        0x52e9dafe WKExtensionMain + 62 (main.m:19)
31  libdyld.dylib                   0x43715e82 start + 2

Thread 1 name:
Thread 1:
0   libsystem_kernel.dylib          0x4381f814 mach_msg_trap + 20
1   libsystem_kernel.dylib          0x4381eece mach_msg + 42 (mach_msg.c:103)
2   CoreFoundation                  0x43b63946 __CFRunLoopServiceMachPort + 152 (CFRunLoop.c:2575)
3   CoreFoundation                  0x43b5f9de __CFRunLoopRun + 1286 (CFRunLoop.c:2931)
4   CoreFoundation                  0x43b5f23a CFRunLoopRunSpecific + 370 (CFRunLoop.c:3192)
5   Foundation                      0x443bf398 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 180 (NSRunLoop.m:374)
6   Foundation                      0x443bf2b4 -[NSRunLoop(NSRunLoop) runUntilDate:] + 76 (NSRunLoop.m:421)
7   UIKitCore                       0x5badf012 -[UIEventFetcher threadMain] + 140 (UIEventFetcher.m:637)
8   Foundation                      0x444c1b60 __NSThread__start__ + 708 (NSThread.m:724)
9   libsystem_pthread.dylib         0x438ad1ac _pthread_start + 130 (pthread.c:896)
10  libsystem_pthread.dylib         0x438b3f28 thread_start + 20

Thread 2 name:
Thread 2:
0   libsystem_kernel.dylib          0x43836d04 __psynch_cvwait + 24
1   libsystem_pthread.dylib         0x438b02c2 _pthread_cond_wait + 496 (pthread_cond.c:591)
2   libsystem_pthread.dylib         0x438aca4a pthread_cond_wait + 38 (pthread_cancelable.c:558)
3   Foundation                      0x444381f0 -[NSOperation waitUntilFinished] + 446 (NSOperation.m:737)
4   Foundation                      0x444a5302 __NSOPERATIONQUEUE_IS_WAITING_ON_AN_OPERATION__ + 22 (NSOperation.m:2610)
5   Foundation                      0x444222ee -[NSOperationQueue addOperations:waitUntilFinished:] + 128 (NSOperation.m:2618)
6   WatchConnectivity               0x53f9871e __47-[WCSession handleUserInfoResultWithPairingID:]_block_invoke.491 + 540 (WCSession.m:1440)
7   WatchConnectivity               0x53fa5608 -[WCFileStorage enumerateUserInfoResultsWithBlock:] + 1564 (WCFileStorage.m:505)
8   WatchConnectivity               0x53f984ca __47-[WCSession handleUserInfoResultWithPairingID:]_block_invoke_2 + 284 (WCSession.m:1430)
9   Foundation                      0x444a4794 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 10 (NSOperation.m:1541)
10  Foundation                      0x443d2f7a -[NSBlockOperation main] + 74 (NSOperation.m:1560)
11  Foundation                      0x444a63e2 __NSOPERATION_IS_INVOKING_MAIN__ + 22 (NSOperation.m:2184)
12  Foundation                      0x443d2b96 -[NSOperation start] + 578 (NSOperation.m:2201)
13  Foundation                      0x444a6b7c __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 22 (NSOperation.m:2215)
14  Foundation                      0x444a6798 __NSOQSchedule_f + 134 (NSOperation.m:2226)
15  libdispatch.dylib               0x436d9846 _dispatch_call_block_and_release + 10 (init.c:1408)
16  libdispatch.dylib               0x436da8b8 _dispatch_client_callout + 6 (object.m:495)
17  libdispatch.dylib               0x436dc6f8 _dispatch_continuation_pop + 330 (inline_internal.h:2484)
18  libdispatch.dylib               0x436dc08c _dispatch_async_redirect_invoke + 520 (queue.c:803)
19  libdispatch.dylib               0x436e6eac _dispatch_root_queue_drain + 540 (inline_internal.h:2525)
20  libdispatch.dylib               0x436e73e0 _dispatch_worker_thread2 + 98 (queue.c:6628)
21  libsystem_pthread.dylib         0x438aecd2 _pthread_wqthread + 158 (pthread.c:2364)
22  libsystem_pthread.dylib         0x438b3f10 start_wqthread + 20

Thread 3:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 4:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 5:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 6 name:
Thread 6:
0   libsystem_kernel.dylib          0x43838c6c kevent_qos + 24
1   libdispatch.dylib               0x436f2b74 _dispatch_kq_poll + 204 (event_kevent.c:736)
2   libdispatch.dylib               0x436f280a _dispatch_kq_drain + 96 (event_kevent.c:809)
3   libdispatch.dylib               0x436f2196 _dispatch_event_loop_poke + 162 (event_kevent.c:1918)
4   libdispatch.dylib               0x436e32b8 _dispatch_mgr_queue_push + 110 (queue.c:5857)
5   WatchConnectivity               0x53f9aa26 -[WCSession createAndStartTimerOnWorkQueueWithHandler:] + 150 (WCSession.m:1803)
6   WatchConnectivity               0x53f90790 -[WCSession onqueue_sendMessageData:replyHandler:errorHandler:dictionaryMessage:] + 382 (WCSession.m:674)
7   WatchConnectivity               0x53f901da __51-[WCSession sendMessage:replyHandler:errorHandler:]_block_invoke.256 + 190 (WCSession.m:630)
8   Foundation                      0x444a4794 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 10 (NSOperation.m:1541)
9   Foundation                      0x443d2f7a -[NSBlockOperation main] + 74 (NSOperation.m:1560)
10  Foundation                      0x444a63e2 __NSOPERATION_IS_INVOKING_MAIN__ + 22 (NSOperation.m:2184)
11  Foundation                      0x443d2b96 -[NSOperation start] + 578 (NSOperation.m:2201)
12  Foundation                      0x444a6b7c __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 22 (NSOperation.m:2215)
13  Foundation                      0x444a6798 __NSOQSchedule_f + 134 (NSOperation.m:2226)
14  libdispatch.dylib               0x436e4c02 _dispatch_block_async_invoke2 + 80 (queue.c:525)
15  libdispatch.dylib               0x436da8b8 _dispatch_client_callout + 6 (object.m:495)
16  libdispatch.dylib               0x436dc6f8 _dispatch_continuation_pop + 330 (inline_internal.h:2484)
17  libdispatch.dylib               0x436dc08c _dispatch_async_redirect_invoke + 520 (queue.c:803)
18  libdispatch.dylib               0x436e6eac _dispatch_root_queue_drain + 540 (inline_internal.h:2525)
19  libdispatch.dylib               0x436e73e0 _dispatch_worker_thread2 + 98 (queue.c:6628)
20  libsystem_pthread.dylib         0x438aecd2 _pthread_wqthread + 158 (pthread.c:2364)
21  libsystem_pthread.dylib         0x438b3f10 start_wqthread + 20

Thread 7:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 8:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 9 name:
Thread 9 Crashed:
0   libdispatch.dylib               0x436eacec dispatch_channel_cancel + 6 (source.c:1020)
1   WatchConnectivity               0x53f909c6 __81-[WCSession onqueue_sendMessageData:replyHandler:errorHandler:dictionaryMessage:]_block_invoke + 42 (WCSession.m:675)
2   libdispatch.dylib               0x436da8b8 _dispatch_client_callout + 6 (object.m:495)
3   libdispatch.dylib               0x436dc6f8 _dispatch_continuation_pop + 330 (inline_internal.h:2484)
4   libdispatch.dylib               0x436ea81e _dispatch_source_invoke + 1758 (source.c:568)
5   libdispatch.dylib               0x436e6eac _dispatch_root_queue_drain + 540 (inline_internal.h:2525)
6   libdispatch.dylib               0x436e73e0 _dispatch_worker_thread2 + 98 (queue.c:6628)
7   libsystem_pthread.dylib         0x438aecd2 _pthread_wqthread + 158 (pthread.c:2364)
8   libsystem_pthread.dylib         0x438b3f10 start_wqthread + 20

Thread 10:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 11:
0   libsystem_pthread.dylib         0x438b3efc start_wqthread + 0

Thread 9 crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x80000000      r2: 0x00000001      r3: 0x7fffffff
    r4: 0x16548f40    r5: 0x00000000      r6: 0x00000110      r7: 0x3f057e58
    r8: 0x00000000    r9: 0x00000000     r10: 0x00000000     r11: 0x00401600
    ip: 0x66e39628    sp: 0x3f057e30      lr: 0x53f909c7      pc: 0x436eacec
  cpsr: 0x80000030

我认为你的朋友是对的:
当您将后台任务设置为完成时,系统将暂停您的应用,请参阅 here
并且系统可以在没有警告的情况下清除暂停的应用程序,请参阅 here
因此,如果发生这种情况,将无法调用 replyHandlererrorHandler,并且应用程序会崩溃。
因此,您不能依赖 sendMessage 来唤醒 iOS 应用程序并及时调用 replyHandler
我建议您在 iOS 上初始化并发症数据的传输。要定期这样做,您可以使用 silent push notifications that wake up your iOS app, and send new complication data using transferCurrentComplicationUserInfo(_:)see here。即使您的手表应用不是运行,也会收到此userInfo,并且可以根据需要更新并发症数据。

编辑:

虽然我上面的解决方案可能有效,但可能有更简单的解决方案:

也许不需要更新您的 ViewModel 来响应 sendMessage,只需要更新并发症。如果是这样,您可以简单地使用 sendMessage 并将 replyHandlererrorHandler 设置为 nil.
sendMessage 保证在将来唤醒 iOS 应用程序,即使 watchOS 应用程序已经处于非活动状态。 iOS 应用程序随后可以发送并发症数据,这些数据将立即显示。
此外,iOS 应用程序可以发送 userInfo 更新您的 ViewModel 作为应用程序上下文。当 watchOS 应用程序再次激活时,它将可用,并且您可以更新 ViewModel.
如果这适合您的用例,您可以简单地删除计时器并在 sendMessage 之后立即完成后台任务。