CloudPhotos : Using CloudKit with iOS is an interesting Apple sample project, last updated a year ago, I thought I would blog about some observations I had.
The table view controller uses an array of CKRecords for its data. I sort of remember Apple saying not to use CKRecord as your model class, but all I could find was this in the docs:
The CKRecord class does not support any special customizations and should not be subclassed. Use this class as-is to manage data coming from or going to the server.
This doesn’t exactly say out-right don’t use as the model, but if they say don’t subclass it then to add your methods I suppose you might try to use a category. The thing is the CKRecord contains useful logic for change tracking, e.g. when modifying a property it stores the key in changedKeys. Then when you come to use a CKModifyRecordsOperation if you use the savePolicy save changed keys then it optimises by only sending those keys that were changed (I think). I suppose the issue here is this is a lot of useful behaviour you wouldn’t want to re-implement yourself in your own model class, so you do in fact want to be using CKRecord as your model class for things that will be sent back to the server. So why Apple say not to subclass it is a bit of a mystery. Anyway once you start to implement a local cache, or would like to make use of core data fetch controller and sorting, you likely will be using NSManagedObjects as your model classes. The CKRecord encodeSystemFields does not include the changedKeys array, I suppose what you can do is track the changed keys yourself on the managed object, and then when you come to transmit the record only set the keys that you want to be sent, and then CKRecord probably won’t mind it is missing the other information.
APLCloudManager is actually a controller, and is init from the app delegate. It sort of the MVC-N design (amazing video well worth the watch), where all network code is inside one controller and not in the view controllers. I say sort of because you are supposed to only manipulate the model inside here, and then model should notify the view controller new data is available, in Apples case their model is in their view controller and is updated via the completionHandler. It’s funny because in the video he says some people choose controller (who disagree controller should only be used in view controllers), and some use manager and right here is a manager example! So in this class we see all the methods for doing cloud network calls. At first I thought the saveRecord was strange because it looks like a direct wrapper around CKDatabase’s saveRecord. However it also has a dispatch back to main queue with in. This is cool because it means the UI view controller callbacks don’t need to worry at all about threading! Perhaps a good design tip to make use of. Maybe they should have at least named it savePhotoRecord though? That’s the only thing its used for. I suppose keeping it generic is ok, given the record itself is what contains the type. Actually one of the weirder things about CloudKit is in one batch you can save records of all different types in one batch, whereas CRUD API consumers would usually be saving one record type to each end point which would need multiple requests.
Again on this manager class, all the methods are void returns, meaning unfortunately there is no cancellation capability. I think Apple should have included cancellation on at least one of them to show people how it is done. Basically I think the method could return the NSOperation (wouldn’t work with convenience methods, would always need full operations) or a protocol (to hide that scary class from UI devs), then that has the cancel method. So for example if trying to refresh a table, then the user moves away, in the view will disappear the request could be cancelled.
The subscribe method in the manager is a particularly nasty one, no completion handler on it. It’s called from the main view controller’s viewDidLoad so if it fails then the user carries on none-the-wiser. Inside the method it does look for a not authenticated error, in which case it keeps retrying every 3 seconds. At this point I noticed the operation’s qualityOfService was not set to user interactive. This means it operates in the NSURLSession discretionary mode where it does fail with network errors and just keeps retrying on its own. I just wonder is this enough to mean that you don’t need any error handling at all? Hence why they didn’t even bother with a completion handler? Will need to look more into that. I think tho at least the user could have none not to leave the app until the subscription had been set up. I was thinking this kind of initialisation feature of checking for account/creating zones/creating subscriptions might be best done in an on-boarding UI screen. You know the kind that is like a welcome screen where there is some info, maybe a spinner while things are being set up then a button? The Apple News app has this on first launch.
In the APLMainTableViewController I’m dissapointed that upon a pull-to-refresh it calls loadPhotos which does a full download of all the photos again, and then it does a full table reload. Ideally we would want to download the changes and then insert/update records. Then we realise they are using the public database which doesn’t support the fetch changes feature, so now we know why they are doing a full download. They still however could have done the table delta updates, but maybe that was too much code for this intro to CloudKit.
Finally I’d like to make a general comment on this old project and why perhaps it is no longer a good beginner sample. CloudKit is all about privacy, so sharing photos to a public database does seem to conflict with that just a bit. In iOS 10 they added CKShare and secure sharing capabilities between friends, it would be great to see a photo sharing sample that uses that instead. I think most people that are struggling with CloudKit are trying to do caching or synchronisation, this sample doesn’t touch on that at all. Maybe there are other Apple CloudKit samples I haven’t discovered yet, but on my wish list would definitely be a proper sharing one, and a sync one (with silent notifications).