You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:56:07