即使函数体内没有任何内容, userNotificationCenter(_:didReceive:) 怎么会导致崩溃?

goh*_*tis 6 ios unusernotificationcenter

我有一个带有通知操作的应用程序。多年来,使用符合UNUserNotificationCenterDelegate其方法并处理其操作的类,一切都运行良好userNotificationCenter(_:didReceive:withCompletionHandler)

\n

最近,我重构了很多我的 app\xe2\x80\x99s 代码以使用 async/await。因此,我切换到委托方法的异步版本,userNotificationCenter(_:didReceive:) async它不再使用完成处理程序。

\n

在发布使用该方法的异步版本的更新后,我开始在我的设备上和野外看到大量崩溃。这里\xe2\x80\x99s 是一个示例,删除了应用程序名称:

\n
Exception Type:  EXC_CRASH (SIGABRT)\nException Codes: 0x0000000000000000, 0x0000000000000000\nTriggered by Thread:  16\n\nApplication Specific Information:\nabort() called\n\n\nLast Exception Backtrace:\n0   CoreFoundation                         0x197ba2248 __exceptionPreprocess + 164\n1   libobjc.A.dylib                        0x190f63a68 objc_exception_throw + 60\n2   Foundation                             0x19252281c _userInfoForFileAndLine + 0\n3   UIKitCore                              0x19aa4fe94 -[UIApplication _performBlockAfterCATransactionCommitSynchronizes:] + 404\n4   UIKitCore                              0x19aa5c20c -[UIApplication _updateStateRestorationArchiveForBackgroundEvent:saveState:exitIfCouldNotRestoreState:updateSnapshot:windowScene:] + 528\n5   UIKitCore                              0x19aa5c4d0 -[UIApplication _updateSnapshotAndStateRestorationWithAction:windowScene:] + 144\n6                                          0x1006c36a8 @objc closure #1 in NotificationDelegate.userNotificationCenter(_:didReceive:) + 132\n7                                          0x1006c37d1 partial apply for @objc closure #1 in NotificationDelegate.userNotificationCenter(_:didReceive:) + 1\n8                                          0x10049d845 thunk for @escaping @callee_guaranteed @Sendable @async () -> () + 1\n9                                          0x1005ceb3d thunk for @escaping @callee_guaranteed @Sendable @async () -> ()partial apply + 1\n10                                         0x1005cea01 specialized thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1\n11                                         0x1005cec75 partial apply for specialized thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1\n12  libswift_Concurrency.dylib             0x1a1dce2d1 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1\n
Run Code Online (Sandbox Code Playgroud)\n

根据测试,当对通知采取操作时会发生崩溃,例如点击通知打开应用程序(响应UNNotificationDefaultActionIdentifier)。

\n

从委托方法中,我将中心和响应传递给异步处理的方法。为了缩小问题范围,我从我的processResponse(_:didReceive:) async方法中一次删除了一个部分,直到只剩下一条打印语句:

\n
func userNotificationCenter(\n    _ center: UNUserNotificationCenter,\n    didReceive response: UNNotificationResponse)\nasync\n{\n    await processResponse(center,\n                          response: response)\n}\n\nprivate func processResponse(_ center: UNUserNotificationCenter,\n                             response: UNNotificationResponse)\nasync\n{\n    print("Do almost nothing...")\n}\n
Run Code Online (Sandbox Code Playgroud)\n

即使是这个最小的例子也会导致崩溃。我如何进一步解决此问题以使该userNotificationCenter(_:didReceive:) async方法正常工作而不会发生这些崩溃?

\n

我设置通知的方式非常简单。在我的 AppDelegate 中,before 中didFinishLaunchingWithOptions,我将NotificationController(负责设置和调度通知)设置为惰性变量,然后将NotificationDelegate(负责处理操作)设置为惰性变量,以便我可以将NotificationController 传递到NotificationDelegate 中:

\n
private lazy var notificationController =\n    NotificationController(statsProvider: statsProvider)\n\nprivate lazy var notificationDelegate: NotificationDelegate =\n    NotificationDelegate(\n        notificationController: notificationController\n    )\n
Run Code Online (Sandbox Code Playgroud)\n

然后,在 中didFinishLaunchingWithOptions,我将委托设置为UNUserNotificationCenter.current()

\n
UNUserNotificationCenter.current().delegate = notificationDelegate\n
Run Code Online (Sandbox Code Playgroud)\n

我不认为任何设置是异常的,除非我缺少某些东西,所以我可以xe2x80x99t 看看几乎空的委托方法如何仍然崩溃。

\n
\n

为了单独说明该问题,我创建了一个演示应用程序来复制我所看到的崩溃。我想知道正确的使用方法userNotificationCenter(_:didReceive:) async委托方法而不崩溃的正确方法。

\n

这个简单的应用程序在启动 5 秒后发送通知。点击通知打开应用程序本应在 5 秒后安排另一个通知,但应用程序崩溃了。

\n

要复制此操作,请使用 UIKit 创建一个新的 iOS 应用程序项目并为其提供推送通知功能。这是 AppDelegate:

\n
import UIKit\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    private lazy var notificationController = NotificationController()\n\n    private lazy var notificationDelegate = NotificationDelegate(\n        notificationController: notificationController\n    )\n\n    func application(_ application: UIApplication,\n                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)\n        -> Bool\n    {\n        UNUserNotificationCenter.current().delegate = notificationDelegate\n\n        return true\n    }\n\n    // MARK: UISceneSession Lifecycle\n\n    func application(_ application: UIApplication,\n                     configurationForConnecting connectingSceneSession: UISceneSession,\n                     options: UIScene.ConnectionOptions)\n    -> UISceneConfiguration\n    {\n        // Called when a new scene session is being created.\n        // Use this method to select a configuration to create the new scene with.\n        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

通知控制器:

\n
import UserNotifications\n\nclass NotificationController {\n\n    init()\n    {\n        Task {\n            await setUpPermissions()\n        }\n    }\n\n    func scheduleNotification()\n    async\n    {\n        let content = UNMutableNotificationContent()\n        content.body = "Test notification"\n        content.sound = .default\n\n        let trigger = UNTimeIntervalNotificationTrigger(\n            timeInterval: 5,\n            repeats: false\n        )\n\n        let request = UNNotificationRequest(\n            identifier: "Test",\n            content: content,\n            trigger: trigger\n        )\n\n        do {\n            try await UNUserNotificationCenter.current()\n                .add(request)\n        }\n        catch {\n            print(error.localizedDescription)\n        }\n    }\n\n    private func setUpPermissions()\n    async\n    {\n        let authorizationStatus = await UNUserNotificationCenter.current()\n            .notificationSettings()\n            .authorizationStatus\n        \n        switch authorizationStatus {\n\n        case .notDetermined:\n\n            do {\n                let granted = try await UNUserNotificationCenter.current()\n                    .requestAuthorization(options: [.sound, .alert])\n                \n                if granted\n                {\n                    await scheduleNotification()\n                }\n            }\n            catch { print(error.localizedDescription) }\n            \n        case .authorized:\n\n            await scheduleNotification()\n\n        default:\n            break\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

通知委托:

\n
import UserNotifications\n\nclass NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {\n    \n    private let notificationController: NotificationController\n    \n    init(notificationController: NotificationController)\n    {\n        self.notificationController = notificationController\n    }\n    \n    func userNotificationCenter(_ center: UNUserNotificationCenter,\n                                willPresent notification: UNNotification)\n    async -> UNNotificationPresentationOptions\n    {\n        return [.banner, .sound]\n    }\n    \n    func userNotificationCenter(_ center: UNUserNotificationCenter,\n                                didReceive response: UNNotificationResponse)\n    async\n    {\n        print("didReceive response")\n        \n        if response.actionIdentifier == UNNotificationDefaultActionIdentifier\n        {\n            await notificationController.scheduleNotification()\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,如果不尝试安排另一个通知,这甚至会崩溃:

\n
func userNotificationCenter(_ center: UNUserNotificationCenter,\n                            didReceive response: UNNotificationResponse)\nasync\n{\n    print("didReceive response")\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如何userNotificationCenter(_:didReceive:) async正确使用它以避免崩溃?

\n

小智 16

我有同样的问题。

看起来断言失败是由在后台线程上调用的方法引起的。

如果您用它注释委托方法@MainActor可以解决问题:

@MainActor
public func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse
) async {
    // implementation
}
Run Code Online (Sandbox Code Playgroud)