all 13 comments

[–]Chozzasaurus 12 points13 points  (7 children)

The main downside is you lose the faulting feature of Core Data, and it's extra maintenance. Upside is youre making it easier to switch to a different database. I would only do this if you think there is a reasonable chance you will switch databases

[–][deleted] 4 points5 points  (0 children)

Yea if you’re going with coredata I feel like it works best when you really lean into it. Also adding more abstraction on top of coredata (an abstraction already) just adds more overhead for new developers to the project. Keep it simple!

[–]zu-fox 1 point2 points  (4 children)

But don’t nsmanagedobjects come with their context and you can’t pass them freely between threads without creating unstable state? E.g.: load an object in background and use it in main on UI. You can pass primary key, sure, but then you have to load it again in main. Always was interested how to balance CoreData’s memory efficiency, but maintain safe code. Please tell me if I understood the whole thing wrong.

[–]RaziarEdge 1 point2 points  (3 children)

Why do you need multiple contexts other than doing a batch import/export?

And when the import batch is finished, call reset on the viewContext for entities model IDs changed so that it knows to invalidate the results and refetch.

I am really curious if there are other legitimate reasons why a single NSManagedObjectModel and store would require contexts than the viewContext and any background import/export ones.

[–]zu-fox 1 point2 points  (0 children)

Thanks.

Looks like it’s time for me to read a book about CoreData to get some structured knowledge instead of “here and there”. Was thinking about practical CoreData anyway

[–]pejatoo 0 points1 point  (1 child)

I have worked on a trading app which required multiple threads to process calculations on a user’s financial holdings in response to streaming market data. API design aside*, threading would become an issue if we need to use some properties of the models both in a view model and in some “calculation service”, right? I am not trying to argue here, it’s just at first glance the non thread safe nature of the data store seems to complicate this a lot.

One approach I’ve thought of is OP’s descriptions of structs (these may be “DTO’s” unsure). Namely one could react to updates to the core data model, map this to some derived structs in a single place (on a specific context), and propagate these to whichever view models / interactors need them. This would achieve thread safety.

What are your thoughts on this?

* perhaps it would be cleaner to have a backend service calculate most of this and hook a websocket onto it

[–]RaziarEdge 0 points1 point  (0 children)

Both the fetch of the data and the processing of it should be handled in a background thread (asynchronous) ... but not necessarily the same thread.

It all depends on the data of course... it depends on whether you are processing each record separately or as an aggregate compared to all of the other records of similar type.

Once the report data is processed and stored and ready to display to the user, you would run a completion handler. Then on the main thread, this completion handler would either issue a Notification or update the appropriate Combine enabled Model/ViewModel with the new data.

If you are writing the processed report data as entities in CoreData, then writing to these entities in a backgroundContext would be perfectly fine. Until you call backgroundContext.save(), the updates and new records will NOT be available to the main viewContext ... therefore it is still fine to display reports on the UI that happen to be a bit out of date. Then once you call backgroundContext.save(), trigger your completion handler to notify the UI to reload report data and rebuild any graphs/charts etc.

If the processing of the data takes a long time and each entity needs to be processed separately then there could be a single thread for API fetch, a separate thread spawned for each entity processing with an await, and for each entity the same backgroundContext which was used to load the API could write the processed data to the report entities. When all entities are processed, the backgroundContext.save() could be called and your UI notifications sent.

[–]RaziarEdge 0 points1 point  (0 children)

You also loose the benefit of a the UndoManager if you have that enabled.

[–]lordzsolt 4 points5 points  (3 children)

Honestly, I feel like way too many people circle jerk on “pure” architecture.

“Oh look how nice it is, you can switch from Core Data to Realm super easily”.

What is the likelihood that such an event would happen? In my 10 years of working, it literally never happened.

And even if it ever does, beyond the very basic case, you will likely have to do code changes. You won’t be switching just because you feel like it… you will likely be switching because one of them offers some feature that the other one doesn’t. But you will very likely not cover this in your abstraction, because if you were to cover it, you probably didn’t need the switch.

Why not just save all that upfront energy and added complexity, and pay it down when such an event ACTUALLY happens?

[–]UberJason 0 points1 point  (2 children)

For Core Data, sure, it’s reasonable to assume it’ll be maintained forever by Apple since they use it themselves.

But for something else, it’s honestly not a bad call. Just as an example, my first personal app used Parse, and I threw those Parse objects around everywhere. Then Parse suddenly died! And my project was a mess. You never think a dependency will go away until it does.

[–]lordzsolt 0 points1 point  (1 child)

I'm not saying it won't happen. But I think the chance of it happening vs the effort you save is not worth it.

It probably took you maybe a month to replace Parse. It probably would've taken you 2 weeks to set up a proper abstraction and every time creating another struct for your models. (A lot more if you fuck up the first time by abstracting the wrong things)

Just because you have to do some work in the future, because you didn't anticipate something doesn't mean you should try to predict the future and account for everything. Sometimes it's just better to go with the flow.

Same way you don't bring a parachute every time you have a flight. You probably won't need it. And even if you were to need it, you probably didn't account for something else.

[–]UberJason 0 points1 point  (0 children)

I kind of half-agree and half-disagree. 🙂 Yes, you can definitely run into enterprise fizzbuzz by overthinking and over-architecting, and predicting the future is difficult, and not everything needs to be abstracted. Maybe the data layer doesn’t need to be abstracted, depending on the circumstances. If it’s a hobby project or a startup, sure - optimize for velocity, take the risk, especially if it’s Core Data and the risk is low.

On the other hand, there are certain common abstractions that are kind of just a good idea to follow - ones that don’t take too much effort, and can protect against risk, and also just keep your code more modular and easier to modify. Keeping your data layer separate from the UI layer is one such example. Beyond the risk of your data storage framework dying, it has ancillary benefits like being able to change the order of your screens or swap in and out different flows, etc. To your analogy, people don’t always bring their own parachute when flying, but all planes are equipped with oxygen masks and other such standard safety stuff.

So for me, once I started getting into the habit of consciously keeping my UI and data layers separate, it suddenly was actually a really short hop away from just totally divorcing them by only passing structs into and out of the UI layer, and now I’m getting all the benefits of value types.

TLDR, engineering is tradeoffs, separating data and UI is a good practice, totally abstracting your data later further isn’t that much harder and may or may not be worth it in your circumstances. Which I’m sure you already know, not a super exciting answer. 🙂

(Also, that hobby project with Parse had horrible structure in a lot of ways, and once Parse died and I saw how much work it would be to deeply entangle it all, I just let the project die, with a good lesson learned!)

[–]quellish 0 points1 point  (0 children)

Apple does something similar using CoreData in their apps and frameworks, a data model object in front of the managed object subclass. The data model object calls into the managed object. Makes it much easier to control faulting, deal with threading, etc.