Iglistkit: Using IGListKit with Realm

Created on 5 Apr 2017  路  17Comments  路  Source: Instagram/IGListKit

New issue checklist

General information

  • IGListKit version: 2.0.0
  • iOS version(s): 10
  • Xcode version: 8
  • Reproducible in the demo project? (Yes/No): No

I am trying to use IGListKit with Realm (config dataSource with realm object). I have annoying issue that performUpdates(animated: Bool, completion: IGListKit.IGListUpdaterCompletion? = nil) use object when it already removed from Realm. I need to check object.isInvalidated but i don't know how to forbid IGListKit use invalidated object. Any ideas?

question

Most helpful comment

I still haven't had an in-depth play with IGListKit (sorry guys!), but maybe I can talk a little bit about how Realm collections work in the hopes a better solution can be found.

Realm collection items are live-updating. Meaning if there is a Realm collection object representing the results of a query, and something else in the app causes the results of that query to change, that previous Realm collection object will be automatically updated with the new results upon the next main run-loop iteration. Without registering for notifications, those mutations are silent, and can easily cause inconsistency exceptions with UITableView if not properly handled.

Realm's granular change notification system was designed specifically in mind so that it could be used to drive the insert/refresh/delete batch operations available in UITableView / UICollectionView. Ie, when the notification block from Realm is triggered, the state of the representing Realm collection object, and the index paths of what changed are compatible with the UITableView data source and delegate expectations at the time.

As long as there's logic to let IGListKit know when a Realm collection has mutated, the two should work together fine. But if IGListKit has a mechanism of internally making copies of collections, that would definitely be an anti-pattern in terms of integrating Realm.

In any case, I'm really busy at the moment (Just moved countries!), so I can't commit any time to trying it out now, but I've got an incoming side-project where I'm considering using IGListKit and Realm together, so at that point, I'll give it a proper try and see how that goes. :)

All 17 comments

@Megaman63 this is probably more a Realm issue than IGListKit, that flag is a realm specific flag about deleted objects if I'm not mistaken?

I'm not really sure there is any better way than manually checking it as you are.

@robertjpayne manually checking do not work. If object has been deleted from Realm, IGListKit may hold link to this object and use it to implement diffable algorithm and app will be crashed. I think, best way to solve this problems - copy realm object and use it, but i am not sure :)

@Megaman63 you can just try something like this


class VC: UIViewController {
    fileprivate var token: NotificationToken?

    fileprivate var users: Results<UserModel> {
        let realm = try! Realm()
        return realm.objects(UserModel)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        token = users.addNotificationBlock { [weak self] changes in
            if case .update = changes {
                self?.adapter.performUpdates(animated: false)
            }
        }
    }

    func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
        return users.map { $0.diffable }
    }

going to close this, but feel free to continue the discussion 馃槃

Calling on @TimOliver 馃槃

Tom Oliver to the rescue 馃槈

ROFL!! Gee whiz, thanks guys. 馃ぃ

Umm, I believe @bigfish24 said he had a play with integrating Realm and IGListKit together and came up against a similar problem. The diffing algorithm doesn't seem to play nice with these ostensibly read-only Realm objects that won't be nil'd out, but can be invalidated if the underlying data is deleted.

I think @MarvinNazari' solution is the best for the time being. You can use a non-managed copy of the object to persist it in memory. It's not great since you lose all of Realm's zero-copy and live updating features, but it should work.

At some point, time permitting, we'll have to see what we can do to make integrating Realm with IGListKit more seamless. :)

The best way to solve the problem that i discovered is completely copy object and use it in
`func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
var list : [IGListDiffable] = []
if let posts = posts, !posts.isInvalidated {
for post in posts {
if !post.isInvalidated {
list.append(post.copiedPost)
}
}
}
return list

}`

@rnystrom, am curious to know if we have any progress in igListKit working with Realm notification. If we use copiedObjects, any changes in Realm won't be reflected in the UI. However, if we add Realm notification to the objects array, it always causes crash as follows:
attempt to delete item 5 from section 0 which only contains 1 items before the update

Any help to solve this crash will be appreciated.

@MichaelLuoSyd I bet that's from how Realm mutates its models. IGListKit is really safest w/ an immutable model architecture. We don't officially support integration w/ Realm (and I've honestly never used Realm), so I can't offer great advice on integrating the two.

@rnystrom, Thanks for your reply. My workaround for igListKit with realm notification is to add notification to realm objects and call adapter.performUpdates in the changeBlock. In isEqual(), only compare object's primary key and those properties which feed the UI to drive the UI update. In didSelectItem(at index:), query Realm according to the object's primary key so the object will always be the updated one. Working as charm.

I still haven't had an in-depth play with IGListKit (sorry guys!), but maybe I can talk a little bit about how Realm collections work in the hopes a better solution can be found.

Realm collection items are live-updating. Meaning if there is a Realm collection object representing the results of a query, and something else in the app causes the results of that query to change, that previous Realm collection object will be automatically updated with the new results upon the next main run-loop iteration. Without registering for notifications, those mutations are silent, and can easily cause inconsistency exceptions with UITableView if not properly handled.

Realm's granular change notification system was designed specifically in mind so that it could be used to drive the insert/refresh/delete batch operations available in UITableView / UICollectionView. Ie, when the notification block from Realm is triggered, the state of the representing Realm collection object, and the index paths of what changed are compatible with the UITableView data source and delegate expectations at the time.

As long as there's logic to let IGListKit know when a Realm collection has mutated, the two should work together fine. But if IGListKit has a mechanism of internally making copies of collections, that would definitely be an anti-pattern in terms of integrating Realm.

In any case, I'm really busy at the moment (Just moved countries!), so I can't commit any time to trying it out now, but I've got an incoming side-project where I'm considering using IGListKit and Realm together, so at that point, I'll give it a proper try and see how that goes. :)

@TimOliver Do you have any updates regarding this topic? We are using Realm and IGListKit in our project and that is really interesting to hear more about best practice here.

@sheff1422
I discovered two ways of dealing with that:
1) use PlainObject instead of Realm object. We have to mirroring protocols:

protocol PlainObject {
    associatedtype Managed: ManagedObject

    var managedObject: Managed { get }
}

protocol ManagedObject {
    associatedtype Plain: PlainObject

    var plainObject: Plain { get }
}

Our PersistenceService convert Realm objects and return only struct witch comply PlainObject protocol and you can use it freely whenever you want

2) Use delayed deletion instead of explicit deletion. Every Realm object comply

protocol DelayedDeleteable {
    var markedAsDeleted: Bool { get set }
    var markedAsDeletedDate: Date { get set }
}

Idea is to only mark objects as deleted and do not delete them directly using realm.delete...
Physical deletion you can make whenever you want (after 5 min, or when application enter background)
This approach will avoid you to have any "object is invalidated" crash. Also i highly recommend this approach when you use CoreData.
Hope this post would be helpful for someone :)

@Megaman63 Thanks for sharing your experience.
Let me clarify couple of thing here:
If i understand correctly your example realm objects are converted to plain objects right after fetch request to realm DB. In this case all benefits of realm lazy loading are gone. So request takes longer in this case compared to using realm objects.
In my case i have about 5000 objects which i am grouping in different IGListSection based on multiple properties after search request.
I tried two different approaches here:
1) To convert realm to plain objects immediately after fetch request. Then plain objects are sorted and grouped according to different criteria. It takes about 3-4 seconds on old devices like ipad 2. So we are looking for some alternative solution.
i supposed that making grouping and sorting based on realm objects might be faster due to the lazy loading nature of realm so i tried next solution.
2) To interact with realm objects on both stages -> after making fetch request and then sort and group it accordingly.
As a result i received some side effects here:

  • initial loading became much more faster on old devices
  • IGListAdapter required objects to support NSCopy protocol so i temporary replaced
-        [self updateObjects:[[dataSource objectsForListAdapter:self] copy] dataSource:dataSource];
+        [self updateObjects:[dataSource objectsForListAdapter:self]  dataSource:dataSource];
  • scrolling became not so smooth as it was before which will really annoy users
  • object deletion issue which was described before is also a problem here

Do you have some suggestions here?

@sheff1422
Yeah, i have some suggestions.
Of course you will loose all benefits of lazy realm objects when you convert 5000 objects to plain at one time. In general, pass 5000 objects to datasource is not good idea...
I think there should be some kind of pagination (or infinite scroll) service, so when collection view scrolls to bottom you request next 20 objects and convert it to plain structs. So you should implement service which would store lazy Result<YouRealmObject> evaluated by some your search predicate and return plain object by your request only when it is necessarily. You should prefer use SortDescriptor instead of your manual sort to retrieve sorted Result<YouRealmObject>

@Megaman63
Currently i have smth similar.
The flow looks like this
1) Fetch section groups with Result in each group
2) When the IGList starts to load sections, it makes that section by section so results are loaded during the scroll. However the main problem here is that lazy loading is performed on the main thread. But due to the complexity of predicate it gives performance issues.
So based on you recommendation it will be better to make that conversion in a background thread while loading sections.
If all goes fine i will post more detailed info here, it may probably help someone.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

omerfarukyilmaz picture omerfarukyilmaz  路  3Comments

shuhrat10 picture shuhrat10  路  3Comments

HarshilShah picture HarshilShah  路  3Comments

rnystrom picture rnystrom  路  3Comments

kanumuri9593 picture kanumuri9593  路  3Comments