【发布时间】:2018-12-05 21:16:44
【问题描述】:
我有一个使用 Core Data 的应用程序。其中有帖子,每个帖子都有许多标签。每个标签都有很多帖子。
我有一个主视图控制器,它显示所有标签的集合视图。此 collectionView 的数据源由 NSFetchedResultsController 提供支持
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,NSFetchedResultsControllerDelegate {
@IBOutlet weak var latestTagsCollectionView: UICollectionView!
var fetchedResultsController: NSFetchedResultsController<Tag>!
var blockOperations: [BlockOperation] = []
override func viewDidLoad() {
super.viewDidLoad()
self.latestTagsCollectionView.dataSource = self
self.latestTagsCollectionView.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Tag")
//3
fetchRequest.sortDescriptors = []
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil) as? NSFetchedResultsController<Tag>
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
func configure(cell: UICollectionViewCell, for indexPath: IndexPath) {
guard let cell = cell as? TagCollectionViewCell else {
return
}
print(indexPath,"indexPath")
let tag = fetchedResultsController.object(at: indexPath)
guard let timeAgo = tag.mostRecentUpdate as Date? else { return }
cell.timeAgo.text = dateFormatter.string(from: timeAgo)
if let imageData = tag.mostRecentThumbnail {
cell.thumbnail.image = UIImage(data:imageData as Data,scale:1.0)
} else {
cell.thumbnail.image = nil
}
cell.tagName.text = tag.name
cell.backgroundColor = UIColor.gray
}
//CollectionView Stuff
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let sectionData = fetchedResultsController.sections?[section] else {
return 0
}
return sectionData.numberOfObjects
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = latestTagsCollectionView.dequeueReusableCell(withReuseIdentifier: "latestTagCell", for: indexPath) as! TagCollectionViewCell
configure(cell: cell, for: indexPath)
return cell
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?){
switch type {
case .insert:
print("Insert Object: \(String(describing: newIndexPath))")
blockOperations.append(
BlockOperation(block: { [weak self] in
if let this = self {
this.latestTagsCollectionView!.insertItems(at: [newIndexPath!])
}
})
)
case .update:
blockOperations.append(
BlockOperation(block: { [weak self] in
if let this = self {
this.latestTagsCollectionView!.reloadItems(at: [newIndexPath!])
}
})
)
case .move:
blockOperations.append(
BlockOperation(block: { [weak self] in
if let this = self {
this.latestTagsCollectionView!.moveItem(at: indexPath!, to: newIndexPath!)
}
})
)
case .delete:
print("deleted record")
blockOperations.append(
BlockOperation(block: { [weak self] in
if let this = self {
this.latestTagsCollectionView!.deleteItems(at: [newIndexPath!])
}
})
)
default: break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
latestTagsCollectionView!.performBatchUpdates({ () -> Void in
for operation: BlockOperation in self.blockOperations {
operation.start()
}
}, completion: { (finished) -> Void in
self.blockOperations.removeAll(keepingCapacity: false)
})
}
deinit {
// Cancel all block operations when VC deallocates
for operation: BlockOperation in blockOperations {
operation.cancel()
}
blockOperations.removeAll(keepingCapacity: false)
}
在不同的视图控制器上,我允许用户添加帖子。对于每个帖子,用户可以添加许多不同的标签。这是保存帖子时调用的方法:
func save(){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
var postTags:[Tag] = []
if let tokens = tagsView.tokens() {
for token in tokens {
let tagFetchRequest: NSFetchRequest<Tag> = Tag.fetchRequest()
tagFetchRequest.predicate = NSPredicate(format: "name == %@", token.title)
do {
let res = try managedContext.fetch(tagFetchRequest)
var tag: Tag!
if res.count > 0 {
tag = res.first
} else {
tag = Tag(context: managedContext)
tag.name = token.title
tag.mostRecentUpdate = NSDate()
tag.mostRecentThumbnail = UIImage(named: "Plus")!.pngData() as! NSData
}
postTags.append(tag)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return
}
}
}
let post = Post(context: managedContext)
for tag in postTags {
tag.addToPosts(post)
post.addToTags(tag)
}
post.mediaURI = URL(string: "https://via.placeholder.com/150")
post.notes = "some notes..."
post.timeStamp = Calendar.current.date(byAdding: .day, value: 8, to: Date()) as! NSDate
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
如您所见,每个标签要么在数据库中被识别,要么在不存在时被创建。对于这个错误,我从数据库中没有数据开始。首先,我创建一个带有“one”标签的帖子。然后我回到主视图控制器的视图。我看到创建了一个名为“One”的新标签。
Insert Object: Optional([0, 0])
在 .insert 大小写应用于 NSFetchedResultsController 的回调方法时打印。
然后我添加一个带有两个标签的帖子:“一”、“二”。
Insert Object: Optional([0, 0])
2018-12-05 12:51:16.947569-0800 SweatNetOffline[71327:19904799] *** Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3698.84.15/UICollectionView.m:5908
2018-12-05 12:51:16.949957-0800 SweatNetOffline[71327:19904799] [error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete item 1 from section 0 which only contains 1 items before the update with userInfo (null)
为什么要在这里删除第 1 项...我不太明白这个错误信息。我相信它应该只是在新的索引路径中插入一个项目,因为在数据库中创建了一个新的标签“Two”。这里发生了什么?
【问题讨论】:
-
您的模型中是否设置了正确的反向关系?如果是这样,您应该将标签添加到帖子或将帖子添加到标签,但不能同时添加。
-
@Paulw11 Tag 实体与目标 Post 和 Inverse “标签”之间存在称为帖子的关系。 Post 实体具有称为标签的关系,带有目标标签和反向“帖子”。我读到这是你如何设置多对多关系,尽管逆向有点令人困惑。我如何知道是将标签添加到帖子还是将帖子添加到标签?在这种情况下,我正在创建一个带有这些标签的帖子,所以我假设只有帖子的标签......
-
不管你用哪种方式,只做一种。我会在新帖子上设置标签
-
任何一种方式都不能消除错误。该行为表明它可能会用新名称(“Two”)覆盖数据库中的原始标签。我意识到,如果我在第二篇文章中使用“Two”而不是“One”、“Two”,则不会出现此问题...
-
我错了。删除数据库并进行更改确实可以解决此问题
标签: ios swift core-data nsfetchedresultscontroller