iOS布局引擎崩溃求助:后台线程违规操作排查
布局引擎后台线程修改引发的NSInternalInconsistencyException崩溃排查
崩溃日志
NSISSparseVectorAddTermWithPlaceValueCoefficientStartingIndex
NSInternalInconsistencyException - 布局引擎被主线程访问后,不可在后台线程修改
背景信息
这是一款日历类APP,通过静默推送通知和后台任务更新本地通知。目前Crashlytics捕获到大量上述崩溃,但无用户反馈该问题,推测崩溃由静默推送或后台任务触发,但无法复现,也缺乏调试思路。
已知信息
- 无法复现崩溃:无论模拟Firebase静默推送、等待后台任务,均无法触发该崩溃。
- 启用Main Thread Checker运行APP,未检测到任何问题。
- 静默推送相关代码已确保所有布局操作在主线程执行:
getCalendarsAndItems()和updatePendingInvites()仅处理EventKit请求和变量更新,无UI操作。代码如下:
import UIKit import FirebaseMessaging extension AppDelegate: MessagingDelegate { private func registerForTopic() { Messaging.messaging().subscribe(toTopic: "refreshNotifications") { error in if let error = error { print("Failed to subscribe to refreshNotifications topic: \(error.localizedDescription)") } } } func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // This delegate method will be called every time the fcm token is retrieved, or changed. guard fcmToken != nil else { return } registerForTopic() } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { registerForTopic() } } extension AppDelegate { // This code gets called when a silent push notification is received func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { EK.shared.eventStore.refreshSourcesIfNecessary() track("Reiceved Silent Push") if UIApplication.shared.applicationState == .active { completionHandler(.noData) return } if let lastRefreshDate = UserDefaults.lastNotificationsRefreshDate, !lastRefreshDate.isOlderThanTenMinutes { if UIDevice.isMac { Task { try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) completionHandler(.noData) }} return } Task { do { try await Task.sleep(nanoseconds: 2 * 1_000_000_000) try await DataStorage.getCalendarsAndItems() await DataStorage.updatePendingInvites() UserNotifications.setBadgeNumber() await UserNotifications.updateNotifications(from: "Silent push") WidgetManager.reloadWidgets() UserDefaults.lastNotificationsRefreshDate = Date() if UIDevice.isMac { try await Task.sleep(nanoseconds: 1 * 1_000_000_000) } completionHandler(.newData) } catch { if UIDevice.isMac { try await Task.sleep(nanoseconds: 1 * 1_000_000_000) } completionHandler(.failed) } } } }
setBadgeNumber方法已调度至主线程执行,代码如下:
static func setBadgeNumber() { // This is used to update the badge number from the foreground guard !isSettingBadgeNumber else { return } isSettingBadgeNumber = true DispatchQueue.main.async { defer { isSettingBadgeNumber = false } #if targetEnvironment(macCatalyst) UIApplication.shared.applicationIconBadgeNumber = 0 // Setting the badge number won't work on Catalyst for some reason unless we set it to zero first #endif if UserDefaults.badgeEnabled { UIApplication.shared.applicationIconBadgeNumber = DataStorage.pendingInvites.count } else { UIApplication.shared.applicationIconBadgeNumber = 0 } } }
- 调用
refreshSourcesIfNecessary()时,可能发送.EKEventStoreChanged通知,被某个VC监听:
NotificationCenter.default.addObserver(self, selector: #selector(eventKitChangesDetected), name: .EKEventStoreChanged, object: nil)
该通知触发以下方法:
@objc func eventKitChangesDetected() { if DataStorage.lastManualUpdate?.isOlderThanFiveSeconds ?? true { print("EventKit changes detected") refreshDataAndLayout() } else { print("EKEventStoreChanged ignored") } }
进而调用:
@objc func refreshDataAndLayout() { DispatchQueue.main.async { if UIApplication.shared.applicationState == .active || UIDevice.isMac { // This method is called when the EKEventStoreChanged is posted. // Because refreshSourcesIfNecessary is called on startup, refreshLayout may end up being called shortly after startup Task.detached(priority: .background) { do { try await DataStorage.getCalendarsAndItems() await DataStorage.updatePendingInvites() if UserDefaults.calendarLayout == .month { await DataStorage.filterMonthlyEvents() } DispatchQueue.main.async { self.refreshLayout() } await UserNotifications.updateNotifications(from: "Home - refreshDataAndLayout") } catch { print("Error fetching data on the App Delegate: \(error)") } } } } }
问题
请问可能是什么原因导致该崩溃?根据App Store Analytics数据,该崩溃每日约发生10次(数据不完全准确),而每台设备每日接收24次静默推送,并非每次推送都会触发崩溃。
堆栈信息
Fatal Exception: NSInternalInconsistencyException 0 CoreFoundation 0xed5e0 __exceptionPreprocess 1 libobjc.A.dylib 0x2bc00 objc_exception_throw 2 CoreAutoLayout 0x57ac NSISSparseVectorAddTermWithPlaceValueCoefficientStartingIndex 3 CoreAutoLayout 0xbbc0 -[NSISEngine _optimizeWithoutRebuilding] 4 CoreAutoLayout 0xb8d4 -[NSISEngine optimize] 5 CoreAutoLayout 0xb660 -[NSISEngine performPendingChangeNotifications] 6 UIKitCore 0x334a8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] 7 UIKitCore 0x32bbc -[_UILabelLayer layoutSublayers] 8 QuartzCore 0x67888 CA::Layer::layout_if_needed(CA::Transaction*) 9 QuartzCore 0x67410 CA::Layer::layout_and_display_if_needed(CA::Transaction*) 10 QuartzCore 0x6d94c CA::Context::commit_transaction(CA::Transaction*, double, double*) 11 QuartzCore 0x66c3c CA::Transaction::commit() 12 QuartzCore 0x1fcd70 CA::Transaction::release_thread(void*) 13 libsystem_pthread.dylib 0x9f48 _pthread_tsd_cleanup 14 libsystem_pthread.dylib 0x4880 _pthread_exit 15 libsystem_pthread.dylib 0x5d7c _pthread_wqthread_legacy_worker_wrap 16 libsystem_pthread.dylib 0x19ec _pthread_wqthread 17 libsystem_pthread.dylib 0x1a04 start_wqthread
内容的提问来源于stack exchange,提问作者CristianMoisei
相关产品推荐
相关产品推荐

