Search code examples
iosswiftrealm

Realm crashing with RLMException "Object has been deleted or invalidated" when deleting from the database


I'm building a project management app in Swift, using Realm to store my data.

I'm using DayViewController to display projects in a TableView. In order to be able to delete projects from the database (and from the UI) via a swipe action, I'm using the tableView:commitEditingStyle:forRowAtIndexPath: function.

However, every time I'm trying to swipe-delete a project from the TableView, the app crashes with a Realm Exception:

Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff23c7127e __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff513fbb20 objc_exception_throw + 48
    2   Realm                               0x0000000105e57b44 _ZL17RLMVerifyAttachedP13RLMObjectBase + 84
    3   Realm                               0x0000000105e5d8fc _ZN12_GLOBAL__N_18getBoxedIN5realm10StringDataEEEP11objc_objectP13RLMObjectBasem + 28
    4   Realm                               0x0000000105e5d8d7 ___ZN12_GLOBAL__N_115makeBoxedGetterIN5realm10StringDataEEEP11objc_objectm_block_invoke + 39
    5   Social Media Management App         0x00000001055d9cdd $s27Social_Media_Management_App17DayViewControllerC05tableF0_12cellForRowAtSo07UITableF4CellCSo0mF0C_10Foundation9IndexPathVtF + 973
    6   Social Media Management App         0x00000001055da055 $s27Social_Media_Management_App17DayViewControllerC05tableF0_12cellForRowAtSo07UITableF4CellCSo0mF0C_10Foundation9IndexPathVtFTo + 165
    7   UIKitCore                           0x00007fff48297462 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 781
    8   UIKitCore                           0x00007fff4826043b -[UITableView _updateVisibleCellsNow:] + 3081
    9   UIKitCore                           0x00007fff4828055f -[UITableView layoutSubviews] + 194
    10  UIKitCore                           0x00007fff485784bd -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2478
    11  QuartzCore                          0x00007fff2b131db1 -[CALayer layoutSublayers] + 255
    12  QuartzCore                          0x00007fff2b137fa3 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 517
    13  QuartzCore                          0x00007fff2b1438da _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 80
    14  QuartzCore                          0x00007fff2b08a848 _ZN2CA7Context18commit_transactionEPNS_11TransactionEd + 324
    15  QuartzCore                          0x00007fff2b0bfb51 _ZN2CA11Transaction6commitEv + 643
    16  QuartzCore                          0x00007fff2b0c04ba _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 76
    17  CoreFoundation                      0x00007fff23bd3867 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    18  CoreFoundation                      0x00007fff23bce2fe __CFRunLoopDoObservers + 430
    19  CoreFoundation                      0x00007fff23bce97a __CFRunLoopRun + 1514
    20  CoreFoundation                      0x00007fff23bce066 CFRunLoopRunSpecific + 438
    21  GraphicsServices                    0x00007fff384c0bb0 GSEventRunModal + 65
    22  UIKitCore                           0x00007fff48092d4d UIApplicationMain + 1621
    23  Social Media Management App         0x00000001055d5feb main + 75
    24  libdyld.dylib                       0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

I've done some research on the topic and from my understanding, this error occurs when the code tries to access an object (or an object's property) after the object has been deleted from the database or invalidated.

However, I'm not quite sure which part of my code is trying to access the deleted object.

DayViewController.swift:

import UIKit
import RealmSwift

class DayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    // MARK: Variables

    @IBOutlet weak var dayViewTableView: UITableView!
    
    // Realm initialization
    let realm = try! Realm()
    
    var currentDate: String = ""
    var Projects: [Project] = []
    var projectsToken: NotificationToken?
    
    // MARK: View Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        Projects = getProjectsForDay(day: currentDate)
        
        // Observe Realm database for changes and reload tableview
        projectsToken = realm.observe { (notification, realm) in
            self.dayViewTableView.reloadData()
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        projectsToken?.invalidate()
    }
    
    // MARK: Datasource / Delegate Methods
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return Projects.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let dayViewCell = dayViewTableView.dequeueReusableCell(withIdentifier: DayViewTableViewCell.reuseIdentifier()) as! DayViewTableViewCell
        
        dayViewCell.setupCellLabels(projectName: Projects[indexPath.row].title, notes: Projects[indexPath.row].notes ?? "")
        
        return dayViewCell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete {
            let objectToDelete = Projects[indexPath.row]
            do {
                try realm.write {
                    realm.delete(objectToDelete)
                }
            }
            catch {
                print("Error trying to delete object from realm database. \(error)")
            }
        }
    }
    
    // MARK: User defined functions
    
    func getProjectsForDay(day: String) -> [Project] {
        let filteredProjects = realm.objects(Project.self).filter("dueDateString == %@", day)
        return Array(filteredProjects)
    }
    
    func setupTableView() {
        dayViewTableView.register(UINib(nibName: DayViewTableViewCell.nibName(), bundle: nil), forCellReuseIdentifier: DayViewTableViewCell.reuseIdentifier())
        dayViewTableView.delegate = self
        dayViewTableView.dataSource = self
        
        dayViewTableView.rowHeight = UITableView.automaticDimension
        dayViewTableView.estimatedRowHeight = 54.0
    }

}

Solution

  • Because your array is not updated after you are deleting and item. // Observe Realm database for changes and reload tableview

    projectsToken = realm.observe { (notification, realm) in
            projects = getProjectsForDay(day: currentDate)
            self.dayViewTableView.reloadData()
        }
    

    This should fix it. Your object will remain in array because realm will not delete it from there, and when tableView will try to reload it's data it will crash at the reference of the deleted item. What are you trying to achieve is done with var projects: Results. And you are doing a bit of overkill observing the whole realm. You can add observer on a specific query, but it's more hard to achieve that kind of live data when you are modifying the day.

    And please don't name properties with capital letters.