1

I am trying to save an array of CKRecords to the documents directory in order to have fast startup and offline access.

Downloading the CKRecords from CloudKit works fine and I am able to use the CKAsset in each record without issue. However, when I save the array of CKRecords that I downloaded to a local file, the CKAsset is not included in the data file. I can tell this from the size of the file saved to the documents directory. If I reconstitute the disk file into an array of CKRecords, I can retrieve all of the fields except the CKAsset. Other than the system fields, and the CKAsset field, all of the fields are Strings.

For testing - I have 10 CloudKit records each with six small String fields and a CKAsset which is about 500KB. When I check the size of the resulting file in documents the file size is about 15KB.

Here's the function to save the array. AppDelegate.ckStyleRecords is a static array of the downloaded CKRecords.

func saveCKStyleRecordsToDisk() {

    if AppDelegate.ckStyleRecords.count != 0 {

        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let docsDirectoryURL = urls[0]
        let ckStyleURL = docsDirectoryURL.appendingPathComponent("ckstylerecords.data")

        do {
            let data : Data = try NSKeyedArchiver.archivedData(withRootObject: AppDelegate.ckStyleRecords, requiringSecureCoding: true)

            try data.write(to: ckStyleURL, options: .atomic)
            print("data write ckStyleRecords successful")

        } catch {
            print("could not save ckStyleRecords to documents directory")
        }

    }//if count not 0

}//saveCKStyleRecordsToDisk

Here is the function to reconstitute the array.

func checkForExistenceOfCKStyleRecordsInDocuments(completion: @escaping ([CKRecord]) -> Void) {

    let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let docsDirectoryURL = urls[0]
    let ckStyleURL = docsDirectoryURL.appendingPathComponent("ckstylerecords.data")

    var newRecords : [CKRecord] = []
    if FileManager.default.fileExists(atPath: ckStyleURL.path) {

        do {
            let data = try Data(contentsOf:ckStyleURL)

            //yes, I know this has been deprecated, but I can't seem to get the new format to work
            if let theRecords: [CKRecord] = try NSKeyedUnarchiver.unarchiveObject(with: data) as? [CKRecord] {
                        newRecords = theRecords
                        print("newRecords.count is \(newRecords.count)")
            }

        } catch {
            print("could not retrieve ckStyleRecords from documents directory")
        }

    }//if exists

    completion(newRecords)

}//checkForExistenceOfckStyleRecordsInDocuments

Calling the above:

    kAppDelegate.checkForExistenceOfCKStyleRecordsInDocuments { (records) in
        print("in button press and records.count is \(records.count)")

        //this is just for test
        for record in records {
            print(record.recordID.recordName)
        }

        AppDelegate.ckStyleRecords = records

    }//completion block

Upon refreshing the tableView that uses the ckStyleRecords array, all data seems correct except the CKAsset (which in this case is a SceneKit scene) is of course missing.

Any guidance would be appreciated.

JohnSF
  • 3,736
  • 3
  • 36
  • 72
  • There is a lot latency in cloudKit, with closure firing when you submitted the request, not when the data is actually available. Try adding a delay of ten seconds [to be sure] after reading, before saving and see if it fixes your problem. If it turns out to be the problem, try using getter/setters perhaps to trigger the save after a local variable is set with the asset value of the data. – user3069232 Jan 26 '19 at 09:47
  • I don't think that is the issue here. I split the functions for testing - I download the CKRecords, place them in an array and then display them in a tableView (including the SCNScene). So I know that I have the data. I then launch the save-to-documents-directory function with a button using that known good array. – JohnSF Jan 26 '19 at 20:29
  • In almost every project I work on I run in to some iOS framework bug. The first time it happened it took me days to figure it out, maybe it is a subtle as that. Work your way thru the code, try and saving just the asset file, forget the strings. Try changing the code you use to save it. – user3069232 Jan 28 '19 at 05:42

1 Answers1

0

A CKAsset was just a file reference. the fileURL property of the CKAsset is where the actual file is located. If you save a SKAsset then you only save the reference to the file. When doing that you do have to remember that this url is on a cache location which could be cleared if you are low on space.

You could do 2 things. 1. when reading your backup CKAsset, then also check if the file is located at the fileURL location. If the file is not there, then read it again from CloudKit. 2. Also backup the file from the fileURl to your documents folder. When you read your CKAsset from your backup, then just don't read the file from fileURL but the location where you have put it in your documents filter.

Edwin Vermeer
  • 13,017
  • 2
  • 34
  • 58