5

In my iOS app I am using the Realm library for storing data. It works great, until for one reason or the other, object get invalidated. (reasons in the specific case could be that I sometimes implement background jobs on the same data that are getting visualized on view, or similar scenarios).

I know why it happens, and I understand is correct behavior but from there on: what is the correct behavior I should implement?

I would love to pull the data from Realm again and continue my job, but when an object gets invalidated, every field is inaccessible and crashes the environment (so, I don't know what is the unique+immutable id of the object).

How can I safely pull the current data from that object again, without storing the id in the ViewController?

Here is some code. I had to edit it heavily to since the structure of the app is different, but the issue is still exemplified. Assume that the table view delegate's is the view and all the technicalities are there.

// A class variable
var events: RLMResults<RLMMyEvent>
// A table view that shows all "MyEvents"
var tableview: UITableView

func initialiseView(_ predicate: NSPredicate) {
  // Get the events with a predicate from Realm
  events = RLMMyEvent.objects(with: predicate)
  tableview.reloadData()
}

// All tableView delegates, and in particular

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! MyCell

   let event = self.events[UInt(indexPath.row)]

   if !event.isInvalidated {
   } else {

   /***** HERE is the problem. What to do here?
          HOW to get those data again, since I cannot 
          get that event's unique ID?  ****/
   }
   cell.initialize(event)
   return cell
}
2
  • Add example code so we can help you more accurately. Commented Aug 27, 2019 at 12:02
  • Thanks, I did it. Commented Aug 27, 2019 at 13:14

3 Answers 3

4
+100

Problem

As I understand, you want to access object's properties when the object is invalidated. Correct me if I'm wrong :)

First of all, let take a look at isInvalidated property

Indicates if the object can no longer be accessed because it is now invalid.

An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if invalidate() is called on that Realm.

It means that an object can be invalidated only when it's managed by a Realm.

My solution

Detach object from the Realm which managers it. If object isn't managed by any Realm, of course it will never be invalidated and you can access properties as you want.

How to do

Whenever you fetch an object from Realm, detach it (create a cloned object).

  1. Add below code to your project. It's used to detach every object in Result after fetching from Realm. I found here

    protocol DetachableObject: AnyObject {
        func detached() -> Self
    }
    
    extension Object: DetachableObject {
    
        func detached() -> Self {
            let detached = type(of: self).init()
            for property in objectSchema.properties {
                guard let value = value(forKey: property.name) else { continue }
    
                if property.isArray == true {
                    //Realm List property support
                    let detachable = value as? DetachableObject
                    detached.setValue(detachable?.detached(), forKey: property.name)
                } else if property.type == .object {
                    //Realm Object property support
                    let detachable = value as? DetachableObject
                    detached.setValue(detachable?.detached(), forKey: property.name)
                } else {
                    detached.setValue(value, forKey: property.name)
                }
            }
            return detached
        }
    }
    
    extension List: DetachableObject {
        func detached() -> List<Element> {
            let result = List<Element>()
    
            forEach {
                if let detachable = $0 as? DetachableObject {
                    let detached = detachable.detached() as! Element
                    result.append(detached)
                } else {
                    result.append($0) //Primtives are pass by value; don't need to recreate
                }
            }
    
            return result
        }
    
        func toArray() -> [Element] {
            return Array(self.detached())
        }
    }
    
    extension Results {
        func toArray() -> [Element] {
            let result = List<Element>()
    
            forEach {
                result.append($0)
            }
    
            return Array(result.detached())
        }
    }
    
  2. Instead of keep events as RLMResults<RLMMyEvent>, keep it as [RLMMyEvent].

  3. After fetching result, detach objects and convert to an array

    events = RLMMyEvent.objects(with: predicate).toArray()
    

Now you can access properties without being afraid of invalidated objects and crash.

Note that detached objects will not be updated if the original objects or their values inside Realm are changed.

3
  • I think this could be a good general solution. I will try to implement it as soon as I can and let you know. Thank you very much! Commented Sep 5, 2019 at 13:16
  • 2
    This fixed an issue I couldn't think of a good solution for for years. Shocked I never stumbled on this solution.
    – NateLillie
    Commented Mar 8, 2024 at 17:50
  • Greate solution, thanks a lot bro! Must have extension for Realm! Commented Jul 17, 2024 at 12:17
1

Have you tried using the RealmObject.isInvalidated variable to check if the object is still valid before you access it? Using this key won't cause the crash you're experiencing.

Current Scenario

realmObject.stringArray.append("")

Suggested Approach

if !realmObject.isInvalidated {
    realmObject.stringArray.append("")
}
1
  • I use IsInvalidated, but my question is How can I safely pull the data from that object again?. IsInvalidated just avoids the crash, but then I have to pull my data again. Commented Aug 27, 2019 at 12:04
1

As a solution, you can observe for changes in the events array, and update/delete your cells accordingly, like

    eventsObserveToken = events.observe { [weak tableView] changes in
        guard let tableView = tableView else { return }

        switch changes {
        case .initial:
            tableView.reloadData()
        case .update(_, let deletions, let insertions, let updates):
            tableView.applyChanges(deletions: deletions, insertions: insertions, updates: updates)
        case .error: break
        }
    }

And an extension

extension IndexPath {
  static func fromRow(_ row: Int) -> IndexPath {
    return IndexPath(row: row, section: 0)
  }
}

extension UITableView {
  func applyChanges(deletions: [Int], insertions: [Int], updates: [Int]) {
    beginUpdates()
    deleteRows(at: deletions.map(IndexPath.fromRow), with: .automatic)
    insertRows(at: insertions.map(IndexPath.fromRow), with: .automatic)
    reloadRows(at: updates.map(IndexPath.fromRow), with: .automatic)
    endUpdates()
  }
}

And I believe that when an object is being invalidated it is being removed from the results array, so your app will not crash.

Or, you can use Unrealm. Unrealm enables you to easily store Swift native Classes, Structs and Enums into Realm without pain. And you don't need to worry about invalidated objects at all, because your app will not crash even if you try to access an invalidated object. Also, your app will not crash if you try to access an object from a different thread.

enter image description here

2
  • This is a very good suggestion. An observer placed in every place that the crash sometimes crashes could be the cleanest solution. Implementing Unrealm is a bit too late, I would have to change a pretty big chunk of the project. I wonder why Realm doesn't give this behavior as an option. Commented Sep 5, 2019 at 13:19
  • @SimoneChelo Actually, its never late to switch. The only modification you need is to conform to Realmable protocol instead of inheriting from Object class and remove the @objc dynamic keywords. Unrealm works with classes as well.
    – arturdev
    Commented Sep 9, 2019 at 18:02

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.