Core Data and Core Location

I’m building an app that is supposed to track user location and sort a bunch of venues based on where the user is on the latitude/longitude coordinate system relative to those venues. I’m using Objective-C for some parts of the app, but my Core Data stack is all Swift 2.2, Xcode 7.3.1 and I’m testing on iOS 9.3.1.
Users can choose between two sort orders: alphabetical, and distance. Additionally, users could filter venues by three categories and one of those categories had three subcategories. On top of the filtering, users can search by name, or by location, so that the name of the place they enter is geocoded. The venues are sorted by distance from that location, closest first.
I had the app working with JSON files and in memory store, but I was building up a lot of technical debt around dictionary keys and adding new features was difficult, so I decided to move over to Core Data. I’d kill two birds with one stone: Clean up the code, and adopt a framework which would offer killer performance if used correctly.
I started with a Core Data stack based on Apple’s default project setup and wrapped up the parts I needed in a separate object that I could pass around my app as needed. I wrote an NSOperation subclass to fetch and ingest my data from a server. That worked pretty flawlessly after a relatively short period of coding, so I was ready to re-implement my sorting, searching, and filtering. After that, I’d move on to new features.
I got table view indices working pretty quickly, because NSFetchedResultsController is designed for that. To boost performance and to group non-alphabetical titles together, I added a property to my model which contained a single sort character. That worked nicely as well.
The next part is to get the venues sorted by distance. It seems easy enough to do with NSSortDescriptor, except to use the basic sort descriptor, you have to use a property that’s stored in the Core Data SQLite database. Core Data doesn’t support using transient properties (such as a calculated CLLocation) with sort descriptors.
I could compute the distance and write to disk whenever the location updates, but the user might be moving while using my app, so I don’t know if that is such a good idea. Updating the results correctly requires updating the stored SQLite which in turn requires writing to disk and then fetching an update. Perhaps objects can be updated in memory, but not saved? How does NSFetchedResultsController handle this?
Thinking about it, I could probably write a second NSOperation whose only job would be to ensure that Core Data keeps my venues sorted properly. Again, this would require a read-write-read cycle but at least it would be in the background. Depending on how much latency there would be and how often the user moves, this might work. I started on this twice, but each time I realized that there’s another issue, and then stopped. That other issue is searching.
Searches on iOS generate a ton of garbage data as the user types, because of the instant results. Worse, I’m geo-coding the results, and then displaying the venues back based on the geo-coded result. Every search hitting disk is probably a very very bad idea. (To be fair, I wait for when I think a user has finished searching to fire off my geocoding request, so this might not be too bad, but it feels too heavy handed.)
I think what’s left is probably to do an in-memory sort. One downside to this approach is that it essentially ruins any memory benefit of Core Data’s faulting, because all of my venues need to be loaded into memory to organize them. I’m trying to figure out if there’s a better way to do this.
I’m beginning to think through a couple of ideas, and I’m posing them here for clarity:
- I wonder if there’s a way to flatten the latitude/longitude pair into a single value that’s independent of the user’s current location or search location. Then, when I get some other location, I can do the same reduction on that location and somehow simplify the sort. Vector math? Reduce?
- Another option is pull out NSDictionaries instead of full objects. Just the coordinate pairs and the object IDs. That might make for a faster sort.
- I just thought of something else. What if I saved all of the venues with a distance from some fixed coordinate (say CLLocation(latitude: 0.0, longitude: 0.0)) and then at runtime, calculate my user’s location (or search location) and compare the distances? That gets us most of the way there, but we’ll get venues that fall along a radius defined by our current distance from the precomputed point. Using that, and the signs of our latitude and longitude, it might be possible to filter, but probably not to sort.
- I could sort in memory. This is what I was doing before, but it’s tricky to get right and stay performant, and I really wish I could get Core Data to help me out here. Additionally, because I’ve got the other filtering going on, it gets confusing to keep track of when to purge the geo-ordered cache, etc. This approach also runs into random crashes with NSFetchedResultsController but I think It’s because I’m being careless with leaving old sorting code lying around.
One other consideration is that the rules of Core Data and NSManagedObject change slightly between Objective-C and Swift. If I were using Objective-C, I could probably get away with fancy property/method overrides that take advantage of the runtime. I can’t do in Swift.
This is one case where I’m probably better off with the power of Objective-C, but the brevity of Swift is too beautiful for me to give on it just yet. This feels like a map-reduce problem, but I’m not sure exactly where to start. I’m going to sleep on this and once I have a satisfactory solution, I’ll do my best to implement it.
This is probably part 1 of n, but I guess we’ll see.