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

使用NSFetchedResultsController的countResultType监听变更崩溃求助

问题描述

尝试用NSFetchedResultsController配合countResultType类型的NSFetchRequest获取Core Data中的对象数量,初始加载数据时能正常返回计数,但数据变更触发代理方法时应用崩溃,报错信息如下:

Thread 1: "Result type of fetched object is not an NSManagedObjectIDResultType, NSDictionaryResultType or NSManagedObjectResultType."

用户提供的代码片段:

查询请求代码

var fetchRequest: NSFetchRequest<NSFetchRequestResult> {
    let fetchRequest: NSFetchRequest<NSFetchRequestResult>
    fetchRequest = MessageEntity.fetchRequest()
    
    fetchRequest.resultType = NSFetchRequestResultType.countResultType
    
    let senderPredicate = NSPredicate(
        format: "senderUserId = %@", contactId
    )

    let recipientPredicate = NSPredicate(
        format: "recipientUserId = %@", contactId
    )
    
    fetchRequest.predicate = NSCompoundPredicate(
        orPredicateWithSubpredicates: [
            senderPredicate,
            recipientPredicate
        ]
    )
    
    let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]
    
    return fetchRequest
}

查询结果控制器代码

lazy var fetchResultsController: NSFetchedResultsController<NSFetchRequestResult> = {
    fetchRequest.resultType = NSFetchRequestResultType.countResultType
    
    let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
    
    fetchedResultsController.delegate = self

    return fetchedResultsController
}()

初始加载与变更监听代码

func loadMessagesFromCoreData() {
    print("loadMessagesFromCoreData: Thread is \(Thread.current) main? \(Thread.isMainThread)")
    
    Task { [weak self] in
        do {
            try self?.fetchResultsController.performFetch()

            guard let entities = self?.fetchResultsController.fetchedObjects
            else { throw AppAlert.error(error: "unable to load messages from core data") }
            print("receiving local messages on load \(String(describing: entities.last))")
        } catch {
            print("Error loading messages from core data:", error.localizedDescription)
        }
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {            
        guard let entities = controller.fetchedObjects
        else { return }
        print("receiving local messages on change \(String(describing: entities.last))")
}

崩溃原因

NSFetchedResultsController(简称FRC)的设计初衷是管理实体对象、实体ID或字典格式的查询结果,并不支持countResultType这种仅返回单一数值的结果类型。虽然初始调用performFetch()时能拿到计数,但当数据发生变更时,FRC内部会自动更新结果集,此时会校验结果类型,发现不符合支持的类型范围,直接抛出错误导致崩溃。


解决方案

方案1:不使用FRC,直接监听上下文变更计数

如果只需要获取数量,不需要管理实体列表,可以直接监听NSManagedObjectContext的对象变更通知,每次收到通知后重新执行计数查询:

// 在初始化方法(如init、viewDidLoad)中添加监听
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(updateMessageCount),
        name: NSManagedObjectContext.objectsDidChangeNotification,
        object: context
    )
}

// 执行计数查询的方法
@objc private func updateMessageCount() {
    do {
        // 构造轻量计数请求,移除排序以优化性能
        let countRequest = MessageEntity.fetchRequest()
        countRequest.predicate = fetchRequest.predicate
        let count = try context.count(for: countRequest)
        print("当前消息数量:\(count)")
    } catch {
        print("计数查询失败:\(error.localizedDescription)")
    }
}

// 在对象销毁时移除监听
deinit {
    NotificationCenter.default.removeObserver(self)
}

这种方案更轻量,不需要维护FRC,适合只需要计数的场景。

方案2:用FRC管理实体,通过列表count获取数量

如果希望保留FRC的变更监听能力(比如需要感知具体的增删改操作),可以修改请求的结果类型为默认的NSManagedObjectResultType,通过fetchedObjects?.count得到数量:

  1. 修改查询请求:
var fetchRequest: NSFetchRequest<MessageEntity> {
    let fetchRequest = MessageEntity.fetchRequest()
    
    // 移除countResultType设置,使用默认的NSManagedObjectResultType
    let senderPredicate = NSPredicate(
        format: "senderUserId = %@", contactId
    )

    let recipientPredicate = NSPredicate(
        format: "recipientUserId = %@", contactId
    )
    
    fetchRequest.predicate = NSCompoundPredicate(
        orPredicateWithSubpredicates: [
            senderPredicate,
            recipientPredicate
        ]
    )
    
    let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]
    
    return fetchRequest
}
  1. 修改FRC定义:
lazy var fetchResultsController: NSFetchedResultsController<MessageEntity> = {
    let fetchedResultsController = NSFetchedResultsController(
        fetchRequest: fetchRequest,
        managedObjectContext: context,
        sectionNameKeyPath: nil,
        cacheName: nil
    )
    fetchedResultsController.delegate = self
    return fetchedResultsController
}()
  1. 修改初始加载与代理方法:
func loadMessagesFromCoreData() {
    print("loadMessagesFromCoreData: Thread is \(Thread.current) main? \(Thread.isMainThread)")
    
    Task { [weak self] in
        do {
            try self?.fetchResultsController.performFetch()
            let count = self?.fetchResultsController.fetchedObjects?.count ?? 0
            print("初始加载消息数量:\(count)")
        } catch {
            print("Error loading messages from core data:", error.localizedDescription)
        }
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<MessageEntity>) {            
    let count = controller.fetchedObjects?.count ?? 0
    print("数据变更后消息数量:\(count)")
}

这种方案可以利用FRC的代理方法精准感知数据变化,同时通过实体列表的count属性得到数量,完全避免了原有的崩溃问题。


内容的提问来源于stack exchange,提问作者abed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:52:52