更新UICollectionView布局时触发[_NSConcreteObservation center]崩溃
解决UICollectionView切换Layout时的
_NSConcreteObservation崩溃问题 我之前碰到过一模一样的崩溃场景,这种错误基本都是UICollectionView内部的布局观察机制冲突导致的,尤其是频繁创建新FlowLayout实例并切换的情况下。结合你的代码和崩溃日志,给你几个靠谱的解决方案:
1. 复用预初始化的布局对象,避免重复创建
每次切换分类都新建UICollectionViewFlowLayout实例,会导致CollectionView内部的观察对象(就是崩溃日志里的_NSConcreteObservation)出现生命周期混乱。建议提前初始化好所有需要的布局,存在ViewController的属性里,切换时直接复用:
首先在类里声明布局属性:
private var videoLayout: UICollectionViewFlowLayout! private var audioLayout: UICollectionViewFlowLayout! private var notesLayout: UICollectionViewFlowLayout!
然后在viewDidLoad里初始化这些布局,顺便把代理设置也一次性搞定:
override func viewDidLoad() { super.viewDidLoad() setupCategoryLayouts() // 提前设置代理,不用每次切换都重复赋值 self.videoListCollectionView.dataSource = self self.videoListCollectionView.delegate = self } private func setupCategoryLayouts() { // 视频分类布局 videoLayout = UICollectionViewFlowLayout() videoLayout.scrollDirection = .vertical videoLayout.itemSize = CGSize(width: UIScreen.main.bounds.width - 10, height: 220) videoLayout.minimumLineSpacing = 10 // 音频分类布局 audioLayout = UICollectionViewFlowLayout() audioLayout.scrollDirection = .vertical audioLayout.itemSize = CGSize(width: UIScreen.main.bounds.width - 10, height: 80) audioLayout.minimumLineSpacing = 10 // 笔记分类布局 notesLayout = UICollectionViewFlowLayout() notesLayout.scrollDirection = .vertical notesLayout.itemSize = CGSize(width: UIScreen.main.bounds.width - 10, height: 80) notesLayout.minimumLineSpacing = 10 }
修改layoutCollectionView方法,直接复用预定义的布局:
func layoutCollectionView() { self.videoListCollectionView.collectionViewLayout.invalidateLayout() switch categoryFilter { case "video": getData() videoListCollectionView.register(UINib(nibName: "MessageItemCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell_Identifier") videoListCollectionView.setCollectionViewLayout(videoLayout, animated: false) case "audio": getData() videoListCollectionView.register(UINib(nibName: "DownloadCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell_Identifier1") videoListCollectionView.setCollectionViewLayout(audioLayout, animated: false) case "notes": videoListCollectionView.register(UINib(nibName: "DownloadNoteCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell_Identifier2") videoListCollectionView.setCollectionViewLayout(notesLayout, animated: false) default: break } videoListCollectionView.reloadData() }
2. 改用直接赋值布局属性替代setCollectionViewLayout
你代码里注释了self.videoListCollectionView.collectionViewLayout = flowLayout,可以尝试用这个方式替换setCollectionViewLayout。后者会触发动画相关的内部观察逻辑,有时候会和频繁切换的场景冲突,直接赋值更简单直接:
// 替换掉原来的setCollectionViewLayout调用 self.videoListCollectionView.collectionViewLayout = flowLayout
记得赋值前先调用invalidateLayout()让旧布局失效。
3. 确保所有UI操作在主线程执行
虽然崩溃日志显示是在主队列,但如果layoutCollectionView是在子线程被调用的,也可能引发这类异常。确保调用该方法时强制在主线程:
DispatchQueue.main.async { self.layoutCollectionView() }
这几个方案里,第一个复用布局的方法应该能直接解决你的崩溃问题,因为它从根源上避免了频繁创建布局导致的内部观察对象混乱。
内容的提问来源于stack exchange,提问作者Arjun D
相关产品推荐
相关产品推荐

