【问题标题】:CoreData Crash iOS 13+14: Exception Type: EXC_BAD_ACCESS (SIGSEGV), Exception Subtype: KERN_INVALID_ADDRESSCoreData Crash iOS 13+14:异常类型:EXC_BAD_ACCESS (SIGSEGV),异常子类型:KERN_INVALID_ADDRESS
【发布时间】:2020-10-06 14:44:23
【问题描述】:

我正在开发的应用程序在应用程序启动时间歇性地引发上述异常。这是触发崩溃的线程的堆栈跟踪。

0   libobjc.A.dylib                 0x00000001bbcaa148 object_getClass + 12 (objc-object.h:237)
1   CoreData                        0x00000001ae28e6b0 _PFObjectIDFastHash64 + 28 (NSBasicObjectID.m:706)
2   CoreFoundation                  0x00000001a84af10c __CFBasicHashRehash + 996 (CFBasicHash.c:477)
3   CoreFoundation                  0x00000001a84b2df4 CFBasicHashRemoveValue + 2352 (CFBasicHash.c:1386)
4   CoreFoundation                  0x00000001a83d12f8 CFDictionaryRemoveValue + 224 (CFDictionary.c:471)
5   CoreData                        0x00000001ae1de8e8 -[NSManagedObjectContext(_NSInternalAdditions) _forgetObject:propagateToObjectStore:removeFromRegistry:] + 120 (NSManagedObjectContext.m:5088)
6   CoreData                        0x00000001ae1be450 -[_PFManagedObjectReferenceQueue _processReferenceQueue:] + 864 (NSManagedObjectContext.m:5077)
7   CoreData                        0x00000001ae2d6578 __90-[NSManagedObjectContext(_NSInternalNotificationHandling) _registerAsyncReferenceCallback]_block_invoke + 68 (NSManagedObjectContext.m:8825)
8   CoreData                        0x00000001ae2ccb18 developerSubmittedBlockToNSManagedObjectContextPerform + 156 (NSManagedObjectContext.m:3880)
9   libdispatch.dylib               0x00000001a80be280 _dispatch_client_callout + 16 (object.m:559)
10  libdispatch.dylib               0x00000001a809a4fc _dispatch_lane_serial_drain$VARIANT$armv81 + 568 (inline_internal.h:2548)
11  libdispatch.dylib               0x00000001a809afe8 _dispatch_lane_invoke$VARIANT$armv81 + 404 (queue.c:3862)
12  libdispatch.dylib               0x00000001a80a4808 _dispatch_workloop_worker_thread + 692 (queue.c:6590)
13  libsystem_pthread.dylib         0x00000001edcd85a4 _pthread_wqthread + 272 (pthread.c:2193)
14  libsystem_pthread.dylib         0x00000001edcdb874 start_wqthread + 8

我在应用启动时使用一系列相互依赖的操作来从 CoreData 获取和清理数据,并通过对服务器的异步调用初始化权限。我使用下面的 CoreDataManager 来处理 CoreData 的保存和获取。

正如我所说,这个问题似乎是随机发生的。任何帮助将不胜感激。

/// A manager for interfacing with Core Data.
///
/// This class is a singleton, and the instance is accessible via the `shared` property.
class CoreDataManager {

    /// The single shared instance of the `CoreDataManager` class.
    static let shared = CoreDataManager()

    /// The underlying `NSManagedObjectContext` that is being used by this manager.
    let context: NSManagedObjectContext

    private let container: NSPersistentContainer

    private init() {
        container = NSPersistentContainer(name: "FinSiteful")
        container.loadPersistentStores(completionHandler: { (_, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        context = container.newBackgroundContext()
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

        NotificationCenter.default.addObserver(self,
                                               selector: #selector(contextDidSave(_:)),
                                               name: Notification.Name.NSManagedObjectContextDidSave,
                                               object: nil)
    }

    /// This function executes any time the managed object context is saved.
    ///
    /// - Important: Other uses of the `notification` paramter
    ///
    ///     1. Use `notification.userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>` to get inserted data
    ///     2. Use `notification.userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>` to get deleted data
    ///     3. Use `notification.userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>` to get updated data
    ///
    /// - Parameter notification: The `Notification` that contains the data updated, inserted, and/or deleted
    @objc private func contextDidSave(_ notification: Notification) {
        print("CoreDataManager: Context did save")
    }

    /// Creates a new managed object within the default context.
    ///
    /// This method mostly exists for convenience. Below is an example of how to use this method to create
    /// a new `Transaction` object which can be saved into CoreData.
    /// ```
    /// let trans: Transaction = CoreDataManager.shared.newObject()
    /// ```
    ///
    /// - Important: The context must be saved after calling this function in order for the object to be permanently
    /// added to the database. Use the `saveContext()` method for this.
    ///
    /// - Returns: A newly created `NSManagedObject`.
    func newObject<T>() -> T where T: NSManagedObject {
        T(context: self.context)
    }

    /// Executes the provided fetch request to retrieve data from Core Data.
    ///
    /// - Parameter request: The `NSFetchRequest` to be executed.
    /// - Returns: The results of `request`.
    func fetch<T>(_ request: NSFetchRequest<T>) -> [T] where T: NSManagedObject {
        do {
            return try context.fetch(request)
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }

    /// Convenience wrapper around the context's delete method.
    ///
    /// Specifies that the given object should be removed from its persistent store when changes are committed.
    ///
    ///  - Important: The context must be saved after calling this function in order for the changes to permanently take effect.
    ///  Use the `saveContext()` method for this.
    ///
    /// - Parameter object: The object to be removed.
    func delete(_ object: NSManagedObject) {
        context.delete(object)
    }

    /// Saves any changes in the context to the database.
    ///
    /// This method will only attempt to save if there are in fact changes that have been made.
    ///
    /// Under normal circumstances, saving the context should never fail.
    /// So, this method produces a `fatalError` in the event of a failure.
    func saveContext() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

更多信息。此错误特别发生在同时运行多个 API 请求的一段代码中。

        let group = DispatchGroup()
        let dispatchSemaphore = DispatchSemaphore(value: 0)
        let dispatchQueue = DispatchQueue(label: "transaction-downloads")
        dispatchQueue.async {
            self.dataCount = 500
            while self.dataCount == self.MAXDATACOUNT {
                group.enter()
                self.saveTransactionsLocal(start: startDate, accessToken: accessToken, accountIDs: accountIDs) { response, error in
                    if response == .success {
                        if self.newEnd == self.end {
                            self.dataCount -= 1
                        }
                        self.end = self.newEnd
                        dispatchSemaphore.signal()
                        group.leave()
                    } else {
                        //Break out of loop
                        print("PH: saving transactions failed")
                        completion(.failure, error)
                        self.dataCount -= 1
                    }
                }
                dispatchSemaphore.wait()
            }
            completion(.success, nil)
        }

长话短说,这段代码从 API 下载数据,将其保存到 CoreData,如果一切顺利,它会再次这样做,直到从 API 接收到的对象数量少于 500(您可以下载的最大值)。关键是在操作期间,这段代码在 for 循环中被多次调用,所以在我的情况下,这段代码为我从中下载数据的 4 个帐户运行了 4 次。

【问题讨论】:

    标签: ios swift core-data crash


    【解决方案1】:

    当使用后台上下文时,你应该使用执行块。

    例如保存时:

    backgroundContext.performAndWait { 
      guard backgroundContext.hasChanges else { return } 
      try? backgroundContext.save()
    }
    

    获取:

    backgroundContext.performAndWait { 
      backgroundContext.fetch(request)
    }
    

    创作:

    backgroundContext.performAndWait { 
      CoreDataObject(context: backgroundContext)
    }
    

    您可以阅读here 如何在后台使用 Core Data,如 Apple 所述:

    因为队列是私有的并且在 NSManagedObjectContext 实例内部,所以只能通过 perform(:) 和 performAndWait(:) 方法访问。 p>

    【讨论】:

    • 从远程 API 下载数据时使用 DispatchQueue 时会出现此错误。基本上,我正在尝试从 API 下载对象,将它们转换为 NSManagedObject,然后通过保存上下文来保存这些对象。独特的部分是对每个 API 请求的一组帐户进行循环,并且在每个请求中是 DispatchQueue。调度队列用于 while 循环,该循环一直运行一段代码,直到下载所有数据,因为每个请求的下载限制可能小于来自 API 的数据总量。
    • 提供 saveTransactionsLocal 的例子
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-17
    • 1970-01-01
    • 2012-02-06
    • 2019-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多