CloudKit Local Caching and Public Databases

Hello,


I am designing a set of apps of software for iOS and macOS and am using CloudKit as the basis for storage. In addition to the question raised below, I would appreciate any feedback on the design that I have created before I commit it to a production environment.


This software project allows auto parts companies to associate usage records with automotive parts.


In this design, there are two types of iCloud records:


  1. Part - This record contains details about a specific part, which has a unique identifier.
  2. PartEvent - This record contains an “event” about the part (inventoried, sold, returned, damaged, etc). There is a CKReference from each PartEvent to its Part.


Each company can have any number of users, each with their own Apple IDs.


Here’s an example of how this could be used:


  • Company A has 500 Part records and some number of PartEvent records for each of those parts. Their iCloud container is iCloud.com.companyA.Parts
  • Company B has 300 Part records and some number of PartEvent records for each of those parts. Their iCloud container is iCloud.com.companyB.Parts
  • If Company C comes along, they get their own container. And so on…


This organization keeps Part and PartEvent data for each company segregated in its own container.


Because the Part and PartEvent data are global to the company, these records are stored in the public database in the container. The private database and shared database aren’t used.


Now onto the iOS app. Its purpose is to look up a Part record via its unique ID, and then add a PartEvent record to indicate something happened to that part. The iOS app is used by users from all companies, so it must work across different company containers.


This is accomplish by using the Apple ID of the logged on user on the device to determine which iCloud container to use.


That is done via a common container, iCloud.com.myCompany.myApp. This container holds the User records for every user of the iOS app. In each user record, there is a field, directedContainer, a string which holds the container name that the user is allowed to access.


Using this method, I can direct someuser@companya.com to iCloud.com.companyA.Parts, then someuser@companyb.com to iCloud.com.companyB.Parts, and so on.


The macOS app is merely used for reporting, and does not add or change any records. It uses the same method to direct a user to a specific container.


The question I have is about local caching of the data. On the iOS app, I don’t do expect to do any caching because the Part records are queried on demand and PartEvent records are added one at a time. However, on the macOS app, where reporting is done, it is desirable to have the Part records cached.


The caching strategy would be to pull down all the records the first time the app is run, and then get notifications from CloudKit when new Part records have appeared.


Apple has provisions for this using private and shared databases as documented here.


However, there appears to be no way to do this with records in the public database. I’m not sure why.


Does anyone have any suggestions for a local caching strategy for global data in a public database?

Replies

1) I am not familiar with using containers to separate groups of users - it might work fine. But if not, just add a field to each record that contains the 'container name' and do a query that requires that the 'container name' be what you expect the container name to be.


2) In the public database you can query against the date last updated this way:

    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"modificationDate>%@",[defaults objectForKey:@"DateLastUpdated"]];
    CKQuery *query = [[CKQuery alloc] initWithRecordType:recordName predicate:predicate];
    CKQueryOperation *theOperation=[[CKQueryOperation alloc] initWithQuery:query];

Thank you for the reply.


Regarding #1, the multiple container stragegy does work -- I've verified it. I like it because conceptually, it's cleaner and compartmentalizes data for different companies into different containers. Thanks for your suggestion thought. It certainly makes sense for an alternate approach.


Regarding #2, that would indeed work if DateLastUpdated is saved by the app between launches. Subscriptions on asset records do work, but they only work while the app is running (e.g. an asset is added by the iOS app while the desktop app is running). The weakness in this modifcationDate/predicate approach is that it doesn't account for records already cached on the Mac app that may have been modified (or deleted) on the server. I'll have to think how to handle those cases.


Again, thanks for adding to the discussion.

Try YapDatabase for caching.