当涉及部分操作时,不会调用 performBatchUpdates 完成处理程序 [英] performBatchUpdates completion handler is not called when there is section operation involved

查看:25
本文介绍了当涉及部分操作时,不会调用 performBatchUpdates 完成处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

到目前为止,根据提供的信息,这里是几乎适用于 NSFetchedResultsController + UICollectionView 的代码片段

  • 对于上面的动画,你会观察到日志

    <代码>>>移动块操作完成>>移动块操作完成>>移动块操作完成

    然而,当一个部分被添加/删除时,performBatchUpdates 的完成处理程序没有被调用!

    对于上面的动画,你会观察到日志

    <代码>>>部分删除>>移动>>节插入>>移动

    这意味着没有执行完成处理程序块!有谁知道为什么会这样,我该如何解决这个问题?

    我希望blockOperations 完成";应该打印出来.预期的日志应该是

    <代码>>>部分删除>>移动块操作完成>>节插入>>移动块操作完成

    谢谢.

    解决方案

    我在 Xcode 12 和 Xcode 13.0 beta 上对此进行了测试.

    在 Xcode 12 上,我可以重现您描述的错误:
    更改对象以删除整个部分时,不会调用完成处理程序.当执行另一个后续更改时,我收到两个完成处理程序调用.

    然而,在 Xcode 13 上,这个问题在我的测试中无法重现.当一个部分被清除并被删除时,我会得到适当的回调.
    尽管如此,我仍然在控制台中收到一条奇怪的消息

    <块引用>

    [Snapshotting] 对至少没有渲染过一次的视图(xxx,StackoverflowDemo.Cell)进行快照需要 afterScreenUpdates:YES.

    此时我的结论是,这是系统中的一个错误,已在 iOS 15 中修复.

    [更新]

    尽管我已经更新了您的代码以在两个操作系统版本上实现正确的行为.

    关键概念是:

    • 首先执行单值更新
    • 第二个执行部分更新
    • 在移动的情况下也在完成块中执行重新加载,否则将不会呈现可能的同时更新

    如果您存储移动的 indexPath 并且仅重新加载这些行,则可以优化最后一步.

    这是我为重现问题而添加的代码.
    我要测试,请执行以下步骤:

    1. 创建一个新的 Xcode 项目
    2. 删除 ViewController、SceneDelegate、Storyboard
    3. 从 info.plist 中删除 Storyboard 和 Scene 引用
    4. 用下面的代码替换 AppDelegate 的内容(只是最小的样板视图/数据设置加上委托方法)

    <预><代码>导入 UIKit导入核心数据@主要的类 AppDelegate: UIResponder, UIApplicationDelegate {变量窗口:UIWindow?func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) ->布尔{self.window = UIWindow(frame: UIScreen.main.bounds)让布局 = UICollectionViewFlowLayout()layout.headerReferenceSize = CGSize(width: 30,height: 30)layout.sectionInset = UIEdgeInsets(顶部:10,左侧:10,底部:10,右侧:10)self.window?.rootViewController = UINavigationController.init(rootViewController: DashboardViewController(collectionViewLayout: layout))self.window?.makeKeyAndVisible()返回真}}类 DashboardViewController:UICollectionViewController {让persistentContainer = PersistentContainer()惰性 var resultsController: NSFetchedResultsController?= {让 fetchRequest = NSFetchRequest(entityName: "Entity")fetchRequest.sortDescriptors = [NSSortDescriptor(key: "section", 升序: true), NSSortDescriptor(key: "name", 升序: false)]让 resultsController = NSFetchedResultsController(fetchRequest: fetchRequest,managedObjectContext: self.persistentContainer.viewContext,sectionNameKeyPath: "节",缓存名称:无)resultsController.delegate = self尝试!resultsController.performFetch()返回结果控制器}()私有变量 itemOperations = [() ->空白]()私有变量 sectionOperations = [() ->空白]()私有变量 reloadRequired = false覆盖 func viewDidLoad() {super.viewDidLoad()self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add))self.collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell")self.collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "Header")}var itemIndex: Int = 0var 部分:双精度 = 0@objc 函数添加(){让实体 = 实体(上下文:self.persistentContainer.viewContext)entity.name = Int64(self.itemIndex)项目索引 += 1entity.section = Int64(floor(self.section))部分 += 0.5尝试!self.persistentContainer.viewContext.save()}覆盖 func numberOfSections(in collectionView: UICollectionView) ->int { return resultsController!.sections?.count ??0 }覆盖 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection 部分: Int) ->Int { return self.resultsController!.sections![section].numberOfObjects }覆盖 func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) ->UICollectionReusableView {let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath) as!标题让 sectionInfo = self.resultsController!.sections?[indexPath.section]header.label.text = sectionInfo?.name返回标题}覆盖 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->UICollectionViewCell {让 item = self.resultsController?.object(at: indexPath)let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as!细胞cell.label.text = String(描述:item?.name??-1)返回单元格}覆盖 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {让 item = self.resultsController?.object(at: indexPath)item?.section = max(0, (item?.section ?? 0) - 1)item?.name = 10 + (item?.name ?? 0)}}@objc(实体)公共类实体:NSManagedObject {@NSManaged 公共变量名:Int64@NSManaged 公共变量部分:Int64}类单元格:UICollectionViewCell {让标签 = UILabel()覆盖初始化(框架:CGRect){super.init(框架:框架)self.backgroundColor = .lightGrayself.label.textAlignment = .centerself.label.autoresizingMask = [.flexibleWidth, .flexibleHeight]self.label.frame = self.contentView.boundsself.label.translatesAutoresizingMaskIntoConstraints = trueself.contentView.addSubview(self.label)}需要初始化?(编码器:NSCoder){fatalError(init(编码器:)尚未实现")}}类标题: UICollectionReusableView {让标签 = UILabel()覆盖初始化(框架:CGRect){super.init(框架:框架)self.backgroundColor = .grayself.label.textAlignment = .centerself.label.autoresizingMask = [.flexibleWidth, .flexibleHeight]self.label.frame = self.boundsself.label.translatesAutoresizingMaskIntoConstraints = trueself.addSubview(self.label)}需要初始化?(编码器:NSCoder){fatalError(init(编码器:)尚未实现")}}类 PersistentContainer:NSPersistentContainer {方便初始化(){//创建对象模型让 nameProperty = NSAttributeDescription()nameProperty.name = "name";nameProperty.attributeType = .integer64AttributeType让 sectionProperty = NSAttributeDescription()sectionProperty.name = 节"sectionProperty.attributeType = .integer64AttributeType让实体 = NSEntityDescription()entity.name = 实体"entity.managedObjectClassName = "Entity";entity.properties = [nameProperty, sectionProperty]让模型 = NSManagedObjectModel()模型.实体.附加(实体)//创建容器self.init(name: Foo", managedObjectModel: 模型)让描述 = NSPersistentStoreDescription()description.type = NSInMemoryStoreTypeself.persistentStoreDescriptions = [描述]self.loadPersistentStores(completionHandler: { (storeDescription, error) in如果让错误 = 错误为 NSError?{致命错误(未解决的错误\(错误),\(错误.userInfo)")}})}}扩展仪表板视图控制器:NSFetchedResultsControllerDelegate {func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {reloadRequired = false如果类型 == NSFetchedResultsChangeType.insert {打印(>>插入")itemOperations.append { [weak self] in如果让 self = self {self.collectionView!.insertItems(at: [newIndexPath!])}}}else if type == NSFetchedResultsChangeType.update {打印(>>更新")itemOperations.append { [weak self] in如果让 self = self,让 indexPath = indexPath {self.collectionView.reloadItems(at: [indexPath])}}}else if type == NSFetchedResultsChangeType.move {打印(>>移动")self.reloadRequired = trueitemOperations.append { [weak self] inif let self = self, let newIndexPath = newIndexPath, let indexPath = indexPath {self.collectionView.moveItem(at: indexPath, to: newIndexPath)}}}else if type == NSFetchedResultsChangeType.delete {打印(>>删除")itemOperations.append { [weak self] in如果让 self = self {self.collectionView!.deleteItems(at: [indexPath!])}}}}func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {如果类型 == NSFetchedResultsChangeType.insert {打印(>>节插入")sectionOperations.append { [weak self] in如果让 self = self {self.collectionView!.insertSections(IndexSet(integer: sectionIndex))}}}else if type == NSFetchedResultsChangeType.update {打印(>>部分更新")sectionOperations.append { [weak self] in如果让 self = self {self.collectionView!.reloadSections(IndexSet(integer: sectionIndex))}}}else if type == NSFetchedResultsChangeType.delete {打印(>>节删除")sectionOperations.append { [weak self] in如果让 self = self {self.collectionView!.deleteSections(IndexSet(integer: sectionIndex))}}}}func controllerDidChangeContent(_ controller: NSFetchedResultsController) {collectionView.performBatchUpdates({ [weak self] () -> Void in守卫让自我=自我其他{返回}//先执行单项操作self.itemOperations.forEach { $0() }//之后执行部分操作self.sectionOperations.forEach { $0() }self.itemOperations.removeAll(keepingCapacity: false)self.sectionOperations.removeAll(keepingCapacity: false)},完成:{【弱自我】(完成)->作废打印(块操作完成")守卫让自我=自我其他{返回}//在移动的情况下重新加载以防项目也发生了变化//否则不会更新如果 self.reloadRequired {self.collectionView.reloadData()}})}}

    So far, here's are the code snippets that almost work for NSFetchedResultsController + UICollectionView, based on the information provided

    Please note that, there are 2 [BlockOperation], as reloadItems and moveItem doesn't play well within single performBatchUpdates. Based on the workaround proposed in the video, we have to call reloadItems in a separate performBatchUpdates.

    We also do not follow 100% methods (Perform reloadItems typed performBatchUpdates first, followed by insert/ move/ delete typed performBatchUpdates) proposed in the video.

    This is because we notice that it doesn't work well even for simple case. Some strange behaviour including reloadItems will cause duplicated cell UI to be shown on screen. The "almost" work method we found are

    • Perform performBatchUpdates for insert, move and delete
    • At completion handler of performBatchUpdates, perform another performBatchUpdates for reloadItems

    NSFetchedResultsController + UICollectionView integration

    private var blockOperations: [BlockOperation] = []
    
    // reloadItems and moveItem do not play well together. We are using the following workaround proposed at
    // https://developer.apple.com/videos/play/wwdc2018/225/
    private var blockUpdateOperations: [BlockOperation] = []
    
    extension DashboardViewController: NSFetchedResultsControllerDelegate {
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
            
            if type == NSFetchedResultsChangeType.insert {
                print(">> insert")
                blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self {
                            self.collectionView!.insertItems(at: [newIndexPath!])
                        }
                    })
                )
            }
            else if type == NSFetchedResultsChangeType.update {
                print(">> update")
                blockUpdateOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self, let indexPath = indexPath {
                            self.collectionView.reloadItems(at: [indexPath])
                        }
                    })
                )
            }
            else if type == NSFetchedResultsChangeType.move {
                print(">> move")
                blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self, let newIndexPath = newIndexPath, let indexPath = indexPath {
                            self.collectionView.moveItem(at: indexPath, to: newIndexPath)
                        }
                    })
                )
            }
            else if type == NSFetchedResultsChangeType.delete {
                print(">> delete")
                blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self {
                            self.collectionView!.deleteItems(at: [indexPath!])
                        }
                    })
                )
            }
        }
        
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
            if type == NSFetchedResultsChangeType.insert {
                print(">> section insert")
                blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self {
                            self.collectionView!.insertSections(IndexSet(integer: sectionIndex))
                        }
                    })
                )
            }
            else if type == NSFetchedResultsChangeType.update {
                print(">> section update")
                blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self {
                            self.collectionView!.reloadSections(IndexSet(integer: sectionIndex))
                        }
                    })
                )
            }
            else if type == NSFetchedResultsChangeType.delete {
                print(">> section delete")
                blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        if let self = self {
                            self.collectionView!.deleteSections(IndexSet(integer: sectionIndex))
                        }
                    })
                )
            }
        }
        
        func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            if blockOperations.isEmpty {
                performBatchUpdatesForUpdateOperations()
            } else {
                collectionView.performBatchUpdates({ [weak self] () -> Void  in
                    guard let self = self else { return }
                    
                    for operation: BlockOperation in self.blockOperations {
                        operation.start()
                    }
                    
                    self.blockOperations.removeAll(keepingCapacity: false)
                }, completion: { [weak self] (finished) -> Void in
                    print("blockOperations completed")
    
                    guard let self = self else { return }
                    
                    self.performBatchUpdatesForUpdateOperations()
                })
            }
        }
        
        private func performBatchUpdatesForUpdateOperations() {
            if blockUpdateOperations.isEmpty {
                return
            }
            
            collectionView.performBatchUpdates({ [weak self] () -> Void  in
                guard let self = self else { return }
                
                for operation: BlockOperation in self.blockUpdateOperations {
                    operation.start()
                }
                
                self.blockUpdateOperations.removeAll(keepingCapacity: false)
            }, completion: { [weak self] (finished) -> Void in
                print("blockUpdateOperations completed")
                
                guard let self = self else { return }
            })
        }
    }
    


    The above way, works "almost" well when no "section" operations involved.

    For the above animation, you will observe logging

    >> move
    blockOperations completed
    >> move
    blockOperations completed
    >> move
    blockOperations completed
    

    However, when a section is being added/ removed, the completion handler of performBatchUpdates is not being called!

    For the above animation, you will observe logging

    >> section delete
    >> move
    >> section insert
    >> move
    

    This means the completion handler block is not executed! Does anyone know why it is so, and how I can workaround with this issue?

    I expect "blockOperations completed" should be printed out. The expected log should be

    >> section delete
    >> move
    blockOperations completed
    >> section insert
    >> move
    blockOperations completed
    

    Thanks.

    解决方案

    I tested this on Xcode 12 and Xcode 13.0 beta.

    On Xcode 12 I can reproduce the bug that you describe:
    When changing an object so that a whole section gets removed, the completion handler is not getting called. When performing another subsequent change, I get two completion handler calls.

    On Xcode 13 however, the issue is not reproducible in my tests. I get proper callbacks when a section gets cleared and is removed.
    Nevertheless I still get an odd message in the console saying

    [Snapshotting] Snapshotting a view (xxx, StackoverflowDemo.Cell) that has not been rendered at least once requires afterScreenUpdates:YES.

    My conclusion at this point is, that this is a bug within the system, that has been fixed in iOS 15.

    [Update]

    Regardless I have updated your code to achieve the proper behavior on both os versions.

    The key concepts are:

    • first execute single value updates
    • second execute section updates
    • in case of a move also execute a reload in the completion block, otherwise possible simultaneous updates will not be rendered

    It may be possible to refine the last step if you store moved indexPaths and only reload these rows.

    This is the code that I added to reproduce the issue.
    I you want to test, please do the following steps:

    1. create a new Xcode project
    2. delete ViewController, SceneDelegate, Storyboard
    3. remove Storyboard and Scene references from info.plist
    4. replace the content of AppDelegate with the code below (just minimal boilerplate view/data setup plus delegate methods)

    
    import UIKit
    import CoreData
    
    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        var window: UIWindow?
        
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            self.window = UIWindow(frame: UIScreen.main.bounds)
            
            let layout = UICollectionViewFlowLayout()
            layout.headerReferenceSize = CGSize(width: 30,height: 30)
            layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
            self.window?.rootViewController = UINavigationController.init(rootViewController: DashboardViewController(collectionViewLayout: layout) )
            self.window?.makeKeyAndVisible()
            return true
        }
    }
    
    class DashboardViewController: UICollectionViewController {
        
        let persistentContainer = PersistentContainer()
        
        lazy var resultsController: NSFetchedResultsController<Entity>? = {
            
            let fetchRequest = NSFetchRequest<Entity>(entityName: "Entity")
            fetchRequest.sortDescriptors = [NSSortDescriptor(key: "section", ascending: true), NSSortDescriptor(key: "name", ascending: false)]
            let resultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                               managedObjectContext: self.persistentContainer.viewContext,
                                                               sectionNameKeyPath: "section",
                                                               cacheName: nil)
            resultsController.delegate = self
            try! resultsController.performFetch()
            return resultsController
        }()
        
        private var itemOperations = [() -> Void]()
        private var sectionOperations = [() -> Void]()
        private var reloadRequired = false
        
        override func viewDidLoad() {
            super.viewDidLoad()
            self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add))
            self.collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
            self.collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "Header")
        }
        
        var itemIndex: Int = 0
        var section: Double = 0
        @objc func add() {
            let entity = Entity(context: self.persistentContainer.viewContext)
            entity.name = Int64(self.itemIndex)
            itemIndex += 1
            entity.section = Int64(floor(self.section))
            section += 0.5
            try! self.persistentContainer.viewContext.save()
        }
        
        override func numberOfSections(in collectionView: UICollectionView) -> Int { return resultsController!.sections?.count ?? 0 }
        
        override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.resultsController!.sections![section].numberOfObjects }
        
        override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
            let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath) as! Header
            let sectionInfo = self.resultsController!.sections?[indexPath.section]
            header.label.text = sectionInfo?.name
            return header
        }
        
        override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let item = self.resultsController?.object(at: indexPath)
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
            cell.label.text = String(describing: item?.name ?? -1)
            return cell
        }
        
        override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            let item = self.resultsController?.object(at: indexPath)
            item?.section = max(0, (item?.section ?? 0) - 1)
            item?.name = 10 + (item?.name ?? 0)
        }
    }
    
    @objc(Entity)
    public class Entity: NSManagedObject {
        @NSManaged public var name: Int64
        @NSManaged public var section: Int64
    }
    
    class Cell: UICollectionViewCell {
        let label = UILabel()
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.backgroundColor = .lightGray
            self.label.textAlignment = .center
            self.label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            self.label.frame = self.contentView.bounds
            self.label.translatesAutoresizingMaskIntoConstraints = true
            self.contentView.addSubview(self.label)
        }
        required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    }
    class Header: UICollectionReusableView {
        let label = UILabel()
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.backgroundColor = .gray
            self.label.textAlignment = .center
            self.label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            self.label.frame = self.bounds
            self.label.translatesAutoresizingMaskIntoConstraints = true
            self.addSubview(self.label)
        }
        required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    }
    
    class PersistentContainer: NSPersistentContainer {
        convenience init() {
            // create object model
            let nameProperty = NSAttributeDescription()
            nameProperty.name = "name"
            nameProperty.attributeType = .integer64AttributeType
            let sectionProperty = NSAttributeDescription()
            sectionProperty.name = "section"
            sectionProperty.attributeType = .integer64AttributeType
            let entity = NSEntityDescription()
            entity.name = "Entity"
            entity.managedObjectClassName = "Entity"
            entity.properties = [nameProperty, sectionProperty]
            let model = NSManagedObjectModel()
            model.entities.append(entity)
            
            // create container
            self.init(name: "Foo", managedObjectModel: model)
            let description = NSPersistentStoreDescription()
            description.type = NSInMemoryStoreType
            self.persistentStoreDescriptions = [description]
            self.loadPersistentStores(completionHandler: { (storeDescription, error) in
                if let error = error as NSError? {
                    fatalError("Unresolved error \(error), \(error.userInfo)")
                }
            })
        }
    }
    
    extension DashboardViewController: NSFetchedResultsControllerDelegate {
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
            
            reloadRequired = false
            if type == NSFetchedResultsChangeType.insert {
                print(">> insert")
                itemOperations.append { [weak self] in
                    if let self = self {
                        self.collectionView!.insertItems(at: [newIndexPath!])
                    }
                }
            }
            else if type == NSFetchedResultsChangeType.update {
                print(">> update")
                itemOperations.append { [weak self] in
                    if let self = self, let indexPath = indexPath {
                        self.collectionView.reloadItems(at: [indexPath])
                    }
                }
            }
            else if type == NSFetchedResultsChangeType.move {
                print(">> move")
                self.reloadRequired = true
                itemOperations.append { [weak self] in
                    if let self = self, let newIndexPath = newIndexPath, let indexPath = indexPath {
                        self.collectionView.moveItem(at: indexPath, to: newIndexPath)
                    }
                }
            }
            else if type == NSFetchedResultsChangeType.delete {
                print(">> delete")
                itemOperations.append { [weak self] in
                    if let self = self {
                        self.collectionView!.deleteItems(at: [indexPath!])
                    }
                }
            }
        }
        
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
            if type == NSFetchedResultsChangeType.insert {
                print(">> section insert")
                sectionOperations.append { [weak self] in
                    if let self = self {
                        self.collectionView!.insertSections(IndexSet(integer: sectionIndex))
                    }
                }
            }
            else if type == NSFetchedResultsChangeType.update {
                print(">> section update")
                sectionOperations.append { [weak self] in
                    if let self = self {
                        self.collectionView!.reloadSections(IndexSet(integer: sectionIndex))
                    }
                }
            }
            else if type == NSFetchedResultsChangeType.delete {
                print(">> section delete")
                sectionOperations.append { [weak self] in
                    if let self = self {
                        self.collectionView!.deleteSections(IndexSet(integer: sectionIndex))
                    }
                }
            }
        }
        
        func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            
            collectionView.performBatchUpdates({ [weak self] () -> Void  in
                guard let self = self else { return }
                
                // execute single item operations first
                self.itemOperations.forEach { $0() }
                // execute section operations afterwards
                self.sectionOperations.forEach { $0() }
                self.itemOperations.removeAll(keepingCapacity: false)
                self.sectionOperations.removeAll(keepingCapacity: false)
            }, completion: { [weak self] (finished) -> Void in
                print("blockOperations completed")
    
                guard let self = self else { return }
    
                // in case of a move do a reload in case the item has also changed
                // it will not update otherwise
                if self.reloadRequired {
                    self.collectionView.reloadData()
                }
            })
        }
    }
    

    这篇关于当涉及部分操作时,不会调用 performBatchUpdates 完成处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆