Core Data is like conversation: Take things out of context, and eventually, it will all come crashing down on you.Me, in an attempt to begin this entry with something witty
ScreenFloat lets you keep visual references to anything you see on your screen floating above other windows using screenshots. It’s also a screenshot organizer.
Disclaimer: Estimated Time of Arrival, Pricing
I don’t do ETAs for my own products.
I’m a solo developer, I have multiple apps that need maintenance and updates, there are just too many moving parts for me to be able to estimate basically anything. And while that may be a serious lack of managerial skill: I accept that flaw and ignore it 🤷♂️.
Regarding pricing, I don’t know what ScreenFloat 2 will cost yet. But I am resolved on its upgrade path: existing customers of ScreenFloat 1 will receive ScreenFloat 2 for free.
About this Journal
I thought it would be fun to chronicle my progress, struggles, successes, failures, struggles, failures, break-throughs, failures, and random stuff while developing ScreenFloat 2. That’s all.
Entry 2 – Core Data
This week, I’ve been busy with getting ScreenFloat 2’s migration to Core Data going and working, the first task being to decide on how I want to handle Core Data in the first place.
No multiple contexts, no problems. Right?
It’s been more or less established that, at the very least, you should have two managed object contexts when working with Core Data: One on the main queue for UI work, and another one on a background queue, for fetching and other “work”, so it doesn’t block the UI of the app.
This can make things tricky, because an object obtained on a background context should not and can not be used on the main queue to update the UI (and vice-versa). It has to be – for the lack of a better word – “converted” to a main queue object so that it can be used there.
So when I began working on migrating to Core Data, I thought, what if I could have the best of both worlds, with only one main queue context? Wouldn’t that solve all my potential problems?
John Hammond: “I’m not making the same mistakes again!”Jurassic Park 2: The Lost World
Ian Malcolm: “No, you’re… making all new ones.”
What if the ScreenFloat app wasn’t aware about its Core Data nature at all? I could do all the Core Data stuff in a separate XPC service, and ScreenFloat would communicate with it to retrieve and update objects. The XPC service is its own process, so doing things there on the main queue context wouldn’t block the UI of ScreenFloat at all.
Sounds too good to be true? That’s because it is.
Issue #1 – Abstraction and Maintenance
To communicate with the XPC service, I’d have to create what I call “translator” classes that I send back and forth from and to the XPC service – classes that ScreenFloat understands (because it wouldn’t know about Core Data entities/objects), and the XPC service understands (so it can “translate” changes back to the Core Data store).
That would result in more work and maintenance. Anytime I wish to update a property, I would not only have to update the Core Data model and its entities, but also these custom “translator” classes.
And what about relationships in the Core Data model? Those would be a pain to handle this way.
Issue #2 – Bloating
ScreenFloat 2 will not only consist of the main app, but also a Share extension, at least.
That would mean to have to have this XPC service available to it as well, which in turn would mean having the XPC service inside the app twice – unnecessary bloat.
Issue #3 – iOS
iOS does not offer XPC services to 3rd party developers. But I want to have the same way of handling Core Data on all platforms ScreenFloat will support (I don’t want to have to do basically the same work twice), so it’s another no-go.
It was nice dreaming and wondering about this, but in the end, the more traditional approach to Core Data is still the best in my case.
Now I have a background queue context – used only for saving to disk -, on top of that the main queue context – used for updating the UI -, on top of that another background queue context – used for actually retrieving and editing stuff -, and on top of that an optional background queue context for work I might want not to persist/save, so it’s easy to discard. It works well so far.
Migrating ScreenFloat v1’s database to v2
I figured, the best way to get Core Data set up, working, and to see if my implementation works correctly, is to code it on-the-fly, adapting and changing it as needed. The best opportunity for this was to work on the migration of ScreenFloat 1’s dual-plist-file-based library. It’s the perfect case to see if my contexts are set up correctly, if conversion from one context to another works as expected, if all data and relationships are populated correctly, if it performs well, and if it saves to disk correctly.
On my MacBook Pro M1 Max, it imports the 699 Shots that currently reside in my ScreenFloat 1 library in less than 0.5 seconds (copying the files to their new destination, reading the plist files, creating the Core Data objects, and saving to disk).
It’s so fast that I had to slow it down artificially, so that the user can at least read that it’s upgrading the Shots library. If it weren’t for that, the progress bar would just be a weird, flashing artefact in the UI, leaving the user clueless as to what just happened. So instead of starting the migration process right after the Splash Screen appears, there’s a little delay, and then the process starts. When it finishes, there’s another short delay, and the Start using ScreenFloat button appears.
Migrating the library also got me thinking about what additional (meta)data I might need further down the road. For instance, ScreenFloat 1 doesn’t do “favorites” for Shots. So an “isFavorite” property would be a good idea.
Another one’s a bit more complex:
Users can put Shots into Categories to organize them. I’d like to store a bit of contextual information for that. For instance: when was this shot added to this category?
If a Shot could only be added to one category, that would be easy – just have an optional “dateAddedToCategory” property. But categories have a multiple-to-multiple relationship with Shots: a category can contain multiple Shots, and Shots can be added to multiple categories.
To my knowledge, the only way to do that is with another Core Data entity (let’s call it “CategoryShotMetadata“) and a many-to-one relationship to both the category and the Shot. When a Shot is added to a category, it automatically creates an object of this metadata entity, populates its “dateAdded” field, and sets up the relationships to the category and the shot. This makes it easy to fetch later: the Shot-to-metadata and Category-to-metadata is a one-to-many relationship (a Metadata object has one Shot and one Category, but a Shot/Category can have multiple Metadata objects). Now, the metadata object can be identified by the combination of its single category and single shot relationships.
I’ve been enjoying my time with Core Data so far (and I’m fully aware that this might change at any given moment 😉 ) . It’s definitely more complex than my previous two-plist-file-approach, but – let’s be frank – Core Data is just better.