Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
248 views
in Technique[技术] by (71.8m points)

ios - App crashes after updating CoreData model that is being displayed in a UITableView

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!

stack trace

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...