Reputation: 4287
I have a strange bug that is extremely rare but causes the app to crash. I can't reproduce it but I finally found a crash report documenting this. (I posted the stack trace below. I used a screenshot, as the quotes function here messed up the formatting. That would be unreadable)
So the problem begins after tapping a button, which calls the method closeButtonTapped
.
This method is supposed to fade out a popup-view
(called ExtendBitPopupView
) and save the text the user entered (details
attribute of one of my data models).
That's the closeButtonTapped
method:
func closeButtonTapped(tap: UITapGestureRecognizer) {
fadeOut { // fadeOut(completion:) just fades out the UI
if self.infoTextView.text != "Enter details..." {
self.entry.info = self.infoTextView.text
self.appDelegate.saveContext()
}
}
}
So it takes the text the user entered and saves it as entry.info
to the database.
Now, a little bit of context: The ExtendBitPopupView
is a popup
that fades in above a UITableView
that displays all entry
objects there are in the database. It's using a NSFetchedResultsController
to manage the data. The table does not show the entry.info
attribute. That is only visible inside the ExtendBitPopupView
According to the stack trace, the app crashes while calling the controllerDidChange
method. I guess it calls this method because an entry
has changed.
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .update: // I guess this case is being used
let cell = tableView.cellForRow(at: indexPath!) as! BitCell
let entry = fetchedResultsController.object(at: indexPath!)
cell.configure(entry: entry)
case .move:
tableView.deleteRows(at: [indexPath!], with: .automatic)
tableView.insertRows(at: [newIndexPath!], with: .automatic)
}
}
Line 224 is mentioned in the crash log. It's this line:
let cell = tableView.cellForRow(at: indexPath!) as! BitCell
I can't figure out why the app could crash at this moment. Also, it does work correctly 99% of the time.
My only observation is that when it happens, I typed in quite a lot of text. But I'm not sure about this, as it only happened like 3-4 times so far.
Does anyone have any ideas? I don't know what I can try and I don't know how to reproduce this bug. If you need any more code, let me know. I just posted the code that is mentioned in the crash log.
Thanks in advance!
Upvotes: 1
Views: 1018
Reputation: 8563
indexPath
is the index BEFORE the deletes and inserts are applied; newIndexPath
is the index AFTER the deletes and inserts are applied.
For updates you don't care where it was BEFORE the inserts and delete - only after - so use newIndexPath
not indexPath
. This will fix crashes that can happen when you an update and insert (or update and delete) at the same time.
For move
the delegate is saying where it moved from BEFORE the inserts and where it should be inserted AFTER the inserts and deletes. This can be challenging when you have a move and insert (or move and delete). I fixed this by saving all the changes from controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
into three different indexPath arrays: insert, delete and update. When you get a move
add an entry for it in both the insert array and in the delete array. In controllerDidChangeContent:
sort the delete array descending and the insert array ascending. Then apply the changes - first delete, then insert, then update. This will fix crashes that can happens when you have a move and insert (or move and delete) at the same time.
It is the same principle for sections. Save the sections changes in arrays, and then apply the changes in order: deletes (descending), sectionDelete (descending), sectionInserts (ascending), inserts(ascending), updates (any order). Sections can't move or be updated.
Upvotes: 10