RLMException when using Realm through different view controllers


#1

I am using Realm in swift to create a list of favorite products to be saved on the user’s device.

In short, 2 views controllers (A and B) are embeded in a tab bar controller.

  • View controller A displays some products that are fetched from a Firebase database.

  • View controller A also has a “add to favorites” button that adds a product to a Realm instance.

  • View controller B embeds a table view to display the results of this Realm instance

The problem is that when I delete some products from the table view, I get the following error as soon as I go back to view controller A:

Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated

Here is the code I ended up with after getting some help from stackOverflow:
View controller A:

import UIKit
import RealmSwift

class ViewControllerA: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

var products = [Product]()
var scannedProduct = Product()
var currentProduct = Product()
let realm = try! Realm()

@IBOutlet weak var myCollectionView: UICollectionView!
@IBAction func addToFavorites(_ sender: Any) {
    try! realm.write {
        realm.add(currentProduct)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()

    myCollectionView.delegate = self
    myCollectionView.dataSource = self
    myCollectionView.isPagingEnabled = true
    myCollectionView.backgroundColor = UIColor.blue
    layout()
}

override func viewDidAppear(_ animated: Bool) {
    currentProduct = scannedProduct
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return products.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionCell
    cell.productName.text = products[indexPath.item].name
    cell.codeLabel.text = products[indexPath.item].code
    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: myCollectionView.bounds.width, height: myCollectionView.bounds.height)
}

func layout() {
    if let flowLayout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
        flowLayout.scrollDirection = .horizontal
        flowLayout.minimumLineSpacing = 0
    }
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let currentVisibleCell = myCollectionView.visibleCells.first
    let currentIndex = myCollectionView.indexPath(for: currentVisibleCell!)?.item
    currentProduct = products[currentIndex!]
}
}

View Controller B:

class ViewControllerB: UIViewController, UITableViewDataSource, UITableViewDelegate {


let realm = try! Realm()
var favorites: Results<Product>!

@IBOutlet weak var favoritesTableView: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()
    favoritesTableView.delegate = self
    favoritesTableView.dataSource = self
    favoritesTableView.rowHeight = 70
}

override func viewWillAppear(_ animated: Bool) {
    favorites = realm.objects(Product.self)
    favoritesTableView.reloadData()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return favorites.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! favoriteTableViewCell
    let product = favorites[indexPath.row]
    cell.productName.text = product.name
    cell.productCategory.text = product.category
    return cell
}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let product = favorites[indexPath.row]
        try! realm.write {
            realm.delete(product)
        }
        tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.bottom)
    }
}

I noticed that if i delete realm objects that were added in a previous session, I can go back to View controller A without any crash. But If I try to delete favorites added in the current session, the app crashes.

I can’t figure out what I am doing wrong. Can I share a realm instance between two view controllers like I did?
Thank you for your precious help


#2

You could share a realm instance between multiple view controllers if you instantiate it at a higher level (e.g. in your AppDelegate). However, I’ve always instantiated my realms on a per view controller basis.

Usually, at the class level I’ll just define a realm variable, but leave it uninitialized…so

var realm: Realm?

And then in the viewDidLoad or viewDidAppear I’ll throw the call to connect and run my base query for populating the table.

realm = try! Realm()
queryResult = realm!.objects(someObject.self)

I’m kind of wondering if your realm is instantiating at a weird time and that’s why this is happening to you. What kind of logging are you seeing on the crash?