【问题标题】:watchOS background task crashes on setTaskCompletedwatchOS 后台任务在 setTaskCompleted 上崩溃
【发布时间】:2018-04-30 16:08:53
【问题描述】:

在我的手表扩展委托init 函数中,我在WCSession 上设置了KVO 观察者:

if WCSession.isSupported() {
  let defaultSession = WCSession.default
  defaultSession.addObserver(self, forKeyPath: "activationState", 
                             options: [.old, .new], 
                             context: &ExtensionDelegate.wcSessionKVOcontext)
  defaultSession.addObserver(self, forKeyPath: "hasContentPending", 
                             options: [.old, .new], 
                             context: &ExtensionDelegate.wcSessionKVOcontext)
}

为了完成所有watch后台任务,这里调用了函数

  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &ExtensionDelegate.wcSessionKVOcontext {
      printStatusChanges(keyPath: keyPath, change: change)
      DispatchQueue.main.async {
        self.completeAllTasksIfReady()
      }
    }
  }

  private func completeAllTasksIfReady() {
    let session = WCSession.default
    // the session's properties only have valid values if the session is activated, so check that first
    if session.activationState == .activated && !session.hasContentPending {
            if wcBackgroundTasks.isEmpty {
                print("No background tasks")
            } else {
                wcBackgroundTasks.forEach { $0.setTaskCompleted() }
                print("\(wcBackgroundTasks.count) connectivity background tasks completed")
            }
      wcBackgroundTasks.removeAll()
    }
  }

通常,这可以正常工作。
但是我在以下日志中崩溃了:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x1db2eda8 __abort_with_payload + 24
1   libsystem_kernel.dylib          0x1db2ae60 abort_with_payload_wrapper_internal + 60
2   libsystem_kernel.dylib          0x1db2ae24 abort_with_payload_wrapper_internal + 0
3   WatchKit                        0x2ddfce86 -[WKRefreshBackgroundTask setTaskCompleted] + 414
4   Watch Extension                 0x00037258 closure #1 in ExtensionDelegate.completeAllTasksIfReady() + 209496 (ExtensionDelegate.swift:196)
5   Watch Extension                 0x000372b0 thunk for @callee_guaranteed (@owned WKRefreshBackgroundTask) -> (@error @owned Error) + 209584 (ExtensionDelegate.swift:0)
6   Watch Extension                 0x0003af08 partial apply for thunk for @callee_guaranteed (@owned WKRefreshBackgroundTask) -> (@error @owned Error) + 225032 (ExtensionDelegate.swift:0)
7   libswiftCore.dylib              0x00525350 0x2fc000 + 2265936
8   libswiftCore.dylib              0x00433b7c 0x2fc000 + 1276796
9   libswiftCore.dylib              0x00304060 0x2fc000 + 32864
10  Watch Extension                 0x00036eec ExtensionDelegate.completeAllTasksIfReady() + 208620 (ExtensionDelegate.swift:196)
11  Watch Extension                 0x000350d4 closure #1 in ExtensionDelegate.observeValue(forKeyPath:of:change:context:) + 200916 (ExtensionDelegate.swift:108)
12  Watch Extension                 0x0000993c _T0Ieg_IeyB_TR + 22844 (AlertManager.swift:0)
13  libdispatch.dylib               0x1d9d3456 _dispatch_call_block_and_release + 10
14  libdispatch.dylib               0x1d9d3432 _dispatch_client_callout + 6
15  libdispatch.dylib               0x1d9e3604 _dispatch_main_queue_callback_4CF$VARIANT$mp + 858
16  CoreFoundation                  0x1df7db1e __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 10
17  CoreFoundation                  0x1df7b73c __CFRunLoopRun + 932
18  CoreFoundation                  0x1dec7660 CFRunLoopRunSpecific + 534
19  GraphicsServices                0x1fb7ab3e GSEventRunModal + 94
20  UIKit                           0x24746604 UIApplicationMain + 156
21  libxpc.dylib                    0x1dcf7b14 _xpc_objc_main + 586
22  libxpc.dylib                    0x1dcf94a8 xpc_main + 154
23  Foundation                      0x1e9b2cf2 service_connection_handler + 0
24  PlugInKit                       0x2525c06e -[PKService run] + 676
25  WatchKit                        0x2de1b036 main + 162
26  libdyld.dylib                   0x1da2e782 start + 2  

很明显,当在WKRefreshBackgroundTask 任务之一中调用setTaskCompleted 时会发生崩溃。

但是可能是什么原因,或者如何调试呢?

【问题讨论】:

  • 不确定它为什么重要,但您可以尝试使用 $0.setTaskCompletedWithSnapshot(false)。这是 watchOS 4 的新功能
  • @Zachary Bell:感谢您的建议。我会尝试。当然,问题在于这显然是一次非常罕见的崩溃。
  • 我无法证明这一点,但我认为苹果方面在内存管理和后台任务处理方面仍然存在一些错误。我怀疑手表有时会耗尽内存,导致后台崩溃。虽然很难调试。手表重启修复了这里的几个问题。
  • @Cobra 感谢您的评论。如果根本无法重现,您认为发送错误报告是否值得?
  • 不疼。但是,Apple 会要求提供详细信息以进行重现,因此至少有一个日志会给他们一些东西。如果他们已经知道,他们至少会将其标记为其他内容的副本,因此您会知道他们正在调查。根据我的经验,在那之后很长一段时间内你可能什么也听不见……

标签: wcsession watchos-4 wkrefreshbackgroundtask


【解决方案1】:

我的错。 Apple 回复了我的错误报告:

此问题的行为与预期基于以下几点:
- 这是一个断言,以确保同一任务不会多次完成
- 这由“调用WKWatchConnectivityRefreshBackgroundTask: 0x16e7b230 表示,这已经 已完成”
- 这可能指向项目代码中的一个(或多个)错误,而不是 SDK/API

因此,我仔细检查了我的代码中是否有可能多次调用WKWatchConnectivityRefreshBackgroundTask 函数setTaskCompleted。确实是这样:

当我开始开发我的手表扩展时,我复制了Apple demo code。它包含以下功能:

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for backgroundTask in backgroundTasks {
        if let wcBackgroundTask = backgroundTask as? WKWatchConnectivityRefreshBackgroundTask {
            // store a reference to the task objects as we might have to wait to complete them
            self.wcBackgroundTasks.append(wcBackgroundTask)
        } else {
            // immediately complete all other task types as we have not added support for them
            backgroundTask.setTaskCompleted()
        }
    }
    completeAllTasksIfReady()
}

这里,应该处理的WKWatchConnectivityRefreshBackgroundTask 存储在数组wcBackgroundTasks 中。稍后在completeAllTasksIfReady() 这个数组中的所有任务都由wcBackgroundTasks.forEach { $0.setTaskCompleted() } 完成。

我的错是我将我的WKWatchConnectivityRefreshBackgroundTask 存储在这个数组中(后来为它调用了setTaskCompleted)并立即处理它并设置了setTaskCompleted。所以这被调用了两次。
感谢发现这个的 Apple 工程师。

【讨论】:

  • 我印象深刻。我遇到过苹果的反应很慢的情况。这是他们的一个很好的回应!
猜你喜欢
  • 2011-08-08
  • 1970-01-01
  • 1970-01-01
  • 2019-12-26
  • 1970-01-01
  • 2018-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多