Search code examples
iosswiftrealm

What to do when object on realm gets invalidated


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
}

Solution

  • 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.