Trouble saving using cachePolicy( . returnCacheDataElseLoad)

Hi -
My client app (IOS app using ParseSwift v4.14.2) needs to save data to the local Parse instance while the app is offline. The example:

  • The app connects to a bluetooth device
  • The app logic wants to record the timestamp of the connection in Parse
  • The Parse initialization configuration includes [requestCachePolicy: .returnCacheDataElseLoad]
  • The app logic can successfully query the Parse db and retrieve the correct object using an explicit cachePolicy( .returnCacheDataElseLoad)
  • Values on the returned object can be changed.
  • The updated object cannot be saved using save()
  • I have tested using multiple cachePolicy settings but with no success

The code:

Client side parse initialization

private func initializeParse() {
    
    ParseSwift.initialize(
      applicationId: CONST_PARSE_LOG_SERVER_APP_ID,
      serverURL: URL(string: CONST_PARSE_LOG_SERVER_URL)!,
      usingTransactions: false,
      usingEqualQueryConstraint: false,
      usingPostForQuery: false,
      requestCachePolicy: .returnCacheDataElseLoad ,
      cacheMemoryCapacity: 512_000,
      cacheDiskCapacity: 10_000_000
    )
  }

App logic:

  public func logDeviceConnection(uuid: String)  {
    
    let newConnection = Int64(Date().timeIntervalSince1970)
    
    var options = API.Options()
    options.insert(.cachePolicy( .returnCacheDataElseLoad ))
    
    let query = Snap.query("device_uuid" == uuid)
    
    query.first(options: options) { result in    // << note the cache policy on query works
      
      switch result {
        case .success(let foundDevice):       // << this works with no network connection
          
          // found the correct device, make mergeable
          var deviceToSave = foundDevice.mergeable
          
          // set the new timestamp on the last_connection member
          deviceToSave.last_connection = newConnection
          
          // atempt to save device
          deviceToSave.save(options: options) { attemptSave in
            switch attemptSave {
              case .success(let savedDevice):
                print("Woo hoo! Saved the new timestamp: \(savedDevice)")
              case .failure(let error):
                print("Doh. Unable to save new timestamop: \(error) ")   // << always fails
            }
          }

        case .failure(let returnedError):
          print("Error: Unable to fetch Parse object.")
      }
    }

Error: “Resource Unavailable”

2022-10-15 12:17:30.514212-0700 CadioTesting[3808:819334] Task <A3AF8CFE-408F-47C1-97AF-6B4C71F71131>.<13> finished with error [-1008] Error Domain=NSURLErrorDomain Code=-1008 "resource unavailable" UserInfo={NSLocalizedDescription=resource unavailable, NSErrorFailingURLStringKey=http://sneakersnaps.com/parse/classes/Snap/RqMR8wxqdP, NSErrorFailingURLKey=http://sneakersnaps.com/parse/classes/Snap/RqMR8wxqdP, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <A3AF8CFE-408F-47C1-97AF-6B4C71F71131>.<13>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <A3AF8CFE-408F-47C1-97AF-6B4C71F71131>.<13>, NSUnderlyingError=0x280d99a70 {Error Domain=kCFErrorDomainCFNetwork Code=-1008 "(null)"}}
Doh. Unable to save new timestamop: ParseError code=-1 error=Unable to connect with parse-server: Error Domain=NSURLErrorDomain Code=-1008 "resource unavailable" UserInfo={NSLocalizedDescription=resource unavailable, NSErrorFailingURLStringKey=http://sneakersnaps.com/parse/classes/Snap/RqMR8wxqdP, NSErrorFailingURLKey=http://sneakersnaps.com/parse/classes/Snap/RqMR8wxqdP, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <A3AF8CFE-408F-47C1-97AF-6B4C71F71131>.<13>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <A3AF8CFE-408F-47C1-97AF-6B4C71F71131>.<13>, NSUnderlyingError=0x280d99a70 {Error Domain=kCFErrorDomainCFNetwork Code=-1008 "(null)"}} 

Research:
I’ve looked at Parse source code for save(). In the synchronous save() case [see ParseObject file, line 1164] there is an additional function named ensureDeepSave() that is called. If you follow that function you come to line 1411 in ParseObject and there is this comment:

 // Remove any caching policy added by the developer as fresh data
 // from the server is needed.
 options.remove(.cachePolicy(.reloadIgnoringLocalCacheData))
 options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))

So the code is overriding my caching selections. Ok hmmm…

In the asynchronous case save() [ParseObject line 1202] the code does not appear to call ensureDeepSave but I still get the error.

Additional thoughts:

  • perhaps my cache is full, over 10meg and that’s causing the error?
  • I’m missing a client side config parameter that allows for query but not save?
  • there is a server side config parameter that I’m not aware of?
  • Obj C version looks like it has a ‘pinning’ feature that ParseSwift doesn’t have

Anyone successfully saving with cachePolicy(.returnCacheDataDontLoad) or .returnCacheDataElseLoad?

thank you!

TLDR cache can be queried (read only) offline, you cannot save (write) to cache offline.

Caching in the Swift SDK is intended for accessing saved data that has already been saved to the server that has been cached locally. This is why querying using find and first works (data was already saved/cached). You mentioned you have no network, connection, this means you can’t save to a Parse Server (your error) and you cannot update the cache (because only successful server responses are cached). You can learn more about cached responses via Apple’s documentation.

The Swift SDK doesn’t have any local storage outside of the aforementioned cache and .current Objects in the Keychain.

It seems you are looking for something like saveEventually in the other SDKs. The Swift SDK doesn’t have this because it doesn’t have local storage. To mimic saveEventualy behavior, you will need to manually save your data using your own local storage when there’s no network connection and then push that data to your server when the connection is available.

Technically the Swift SDK doesn’t need any other local storage as developers can encode/save their own objects to their local storage mediums easily. Even if someone wanted to add saveEventually or something with local storage, it should be protocol based:

More here:

Thanks for the quick reply! (apologies for my tardy response)
This is what I expected.
So hi ho hi ho back to core data I go (to build a local cache to a write-through cache :wink: