Parse PFObject.fetch is not updating to current data from server - ios

In my app, I have a screen where the user can edit an object's attributes. The screen has 2 actions: save or revert.
When revert is selected, I want to revert the object's attributes to whatever is stored on the server and discard all local changes. I do this with the the fetch method:
println("1: \(localObject)")
if localObject.isDirty() {
localObject.fetchInBackgroundWithBlock { serverObject, error in
println("2: \(localObject)")
println("3: \(serverObject)")
}
}
isDirty() works correctly and only returns true if something was locally modified and not saved.
According to the docs, fetch: fetches the PFObject with the current data from the server. But what I see in the log is that all 3 print statements show the same local attributes. I would expect serverObject to be whatever is stored on the server, but it's not, serverObject also shows the locally modified attributes.
I'm not using the Local Datastore.
What is going on? Is this a bug with the Parse SDK? Or is there a caching issue?

If you call fetch without using the local datastore, Parse will give you a error in the console.
Since you're not getting an error from Parse when you call fetchInBackgroundWithBlock, it means that at some point you've called localObject.pinInBackground() and placed that object in the local datastore.
For locally stored objects, you can call revert() before fetchInBackgroundWithBlock to discard local changes before updating data from the server.
if localObject.isDirty() {
localObject.revert()
localObject.fetchInBackgroundWithBlock { serverObject, error in
println("2: \(localObject)")
println("3: \(serverObject)")
}
}

I haven't seen the docs for fetch, but I do know that you can accomplish what you're trying just by performing a query. Doing the following will query from the server (although you can adjust the cache policy if you've already used the query to look at cache or network first):
var query = PFQuery(className:"GameScore")
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error == nil && gameScore != nil {
println(gameScore)
}else {
println(error)
}
}
Alternatively, you could just save a copy of the local object before any changes can be made, and then if it is dirty, you can just replace it with the copy. That's probably what I'd do to reduce the number of parse API calls.

Related

Recover from possibly corrupt Core Data database

I have an iOS app which stores it's data in a Core Data database. The basic structure has Libraries and Items, with a many-to-many relationship between them represented by LibraryItemMapping. The mapping item contains an index, so that items can be sorted differently in different containers. This works fine most of the time, but on my own device which I use for dev/test, I am running into situations where the Core Data objects are returning nil for one side or the other of those relationships. If I enumerate those objects at launch (via code in AppDelegate), the nils disappear and I'm able to retrieve objects on either side of the relationship, but when looking for the same relationships just a few seconds later they begin to return nil. This, for example, always runs OK and never errors out or finds any nils:
public func runConsistencyCheck() {
do {
let mappings = try dataContext.fetch(NSFetchRequest<LibraryItemMapping>(entityName: "LibraryItemMapping"))
for map in mappings {
if (map.item==nil || map.library==nil) {
print("Found corrupt mapping record. Deleting.")
dataContext.delete(map)
}
}
saveContext()
} catch {
print("An error occurred during consistency checking: \(error)")
}
}
But this regularly finds nil for obj.item and/or crashes on my device:
func getMapping(forItem item: Item) -> LibraryItemMapping? {
if (libraryData?.itemMappings != nil) {
for obj in libraryData!.itemMappings as! Set<LibraryItemMapping> {
if (obj.item != nil && obj.item! == item) { return obj }
else {
print("Found Core Data mapping with null item data.")
}
}
}
return nil
}
Attempting to create or modify and then save any other records results in multiple iterations of this error:
"Error Domain=NSCocoaErrorDomain Code=1570 \"item is a required value.\" UserInfo={NSValidationErrorKey=item, NSLocalizedDescription=item is a required value., NSValidationErrorObject=<LibraryItemMapping: 0x174098060> (entity: LibraryItemMapping; id: 0xd0000000001c0000 <x-coredata://ED9F1AE5-A7D0-46FD-AC94-941E9EFEF341/LibraryItemMapping/p7> ; data: {\n index = 4;\n item = nil;\n library = \"0xd000000000140004 <x-coredata://ED9F1AE5-A7D0-46FD-AC94-941E9EFEF341/LibraryData/p5>\";\n})}"
This only happens on the one device that I am aware of, so I'm tempted to put it down to a database corrupted by a bad debug session at some point, but at the very least I'd like to be able to clean up after it gracefully. If this were SQL, I would just run a consistency check and something like delete from LibraryItemMapping where item is null or library is null and move on, but I'm not sure how to do the equivalent with Core Data. How can I troubleshoot this further, and how can I gracefully recover from a seemingly corrupt database?
After pulling the sqlite files from the device and digging through the raw data, this actually turned out to be a data model bug. During app startup it goes through a "prune old records" step, and inconsistent delete rules on the relationships for the linking table allowed it to leave records in an inconsistent state. After correcting the delete rules to use Cascade or Nullify as appropriate, everything is working again.

Parse - downloading only updated rows of Data

I am running parse server on AWS. Data is being stored on mlab. At app launch I make a query:
let query = PFQuery(className: foodDataClassName_)
query.findObjectsInBackgroundWithBlock({(objects : [PFObject]?,error : NSError?) -> Void in
})
It returns me all the rows of data. I save them locally using CoreData. When any row of data is updated, I delete the previous locally stored data and download all the data again and save it. This is not a good approach. What I want is that I only download the rows which are updated not all the rows. How can I achieve that? Thanks.
what you can do is the following:
The first time the user log in to your app you need to query for all the user rows from the server so in this case you will execute the query without any condition. In this query you can use limit in order to limit the results that will be returned by the server. When you get results from the server you will need to:
Store all rows in your local database
Store the current NSDate inside NSUserDefaults
The next call to your server will be to get only the updated rows. In order to achieve it you will need to:
Get the last sync date from your NSUserDefaults (the one that we save above)
Execute the query but this time with a condition of greaterThan your lastSyncDate
at the end your code to fetch the items should look like the following:
// check if we synced the date before
let lastSyncDate = NSUserDefaults.standardUserDefaults().objectForKey("lastSyncDate")
let query = PFQuery(className: "MyParseObjectClassName")
if (lastSyncDate != nil){
// get only records that were created/updated since the last sync date
query.whereKey("updatedAt", greaterThan: lastSyncDate!)
}
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
// store objects in parse local data store
if (objects?.count > 0){
PFObject.pinAllInBackground(objects)
}
// save the last sync date in user defaults
NSUserDefaults.standardUserDefaults().setValue(NSDate(), forKey: "lastSyncDate")
NSUserDefaults.standardUserDefaults().synchronize()
}
Please notice here i used parse local data store which allows you to store parse objects to your local data base easily without using core data. Parse local data store is provided by parse iOS SDK and save a lot of time and effort for you so i strongly recommend you to leverage it.
local data store also take care for you to objects that were created/updated and it will automatically create new object and will update existing ones.
you can read more about it in here

Firebase Offline Store - Query not returning changes in online store

I’m using Firebase with the offline ability set to true.
let ref = FIRDatabase.database().referenceWithPath(“my data”).child(“my users id”)
scoresRef.keepSynced(true)
This path also has keep Synced set to true, as with out this with changes in the database are not seen within the app immediately as it is using the local cache.
I have another top level node / path in my app that I want to search - containing other users.
I want to use a singleEvent query and find an email address, I’m doing this via
studios.queryOrderedByChild("email").queryEqualToValue(email).observeSingleEventOfType(.Value, withBlock: { snapshot in // etc
I am able to find the node, however I keep getting the local cached version and not the most recent one in the firebase online store.
If I make some changes to the node online, I don’t get these back within the fetch.
If I changed my fetch to a monitor type i.e.
studios.queryOrderedByChild("email").queryEqualToValue(email).observeEventType(.Value, withBlock: { snapshot in // etc
I get the local cache node first, then get the online updated version as well.
I would rather use the SingleEvent fetch, but I don’t want to monitor the users entire node with keepSynced as it is a high level node and I don’t want to keep all that data locally, as its not directly related to the user.
One fix I found was prior to the single query was add .keepSynced(true) and in the completion block add .keepSynced(false). I'm not sure how much of the node is downloaded this was and may as well use the monitor fetch rather than the singleEvent.
Should I just use the monitorEvent or is there a better way to use SingleEventFetch that goes to the online store and instead of just returning my local node.
PS I am online and this is confirmed via
var connectedRef = FIRDatabase.database().referenceWithPath(".info/connected")
connectedRef.observeEventType(.Value, withBlock: { snapshot in
let connected = snapshot.value as? Bool
if connected != nil && connected! {
println("Connected")
} else {
println("Not connected")
}
})
Thanks

How to prevent using cache when new data is available?

I'm caching data in CoreData within my app to reduce updating request when there's nothing new.
Here's my caching logic in pseudo code:
if cacheExistsInCoreData {
if cacheIsOutdated {
loadDataFromRemoteAndCacheItWithCurrentDate()
}else {
useCache()
}else {
loadDataFromRemoteAndCacheItWithCurrentDate()
}
How I check if cache is outdated:
func checkIfCacheIsOutdated {
if lastCachedDate isOrderThan selfDefinedCheckingDate {
return true // need to load new data
}else {
return false // just use cache
}
}
This mechanism works fine almost all the time.
While in rare situation I find my program caches the wrong data with a right date, which means user might see the older data and could not get update when new one is available.
If there's nothing wrong with my caching logic, I wonder if the reason could be that when the remote data is fetched by my app before it gets updated and then gets stored in core data with the latest date time.
A cache in core data includes:
data(provided by remote server) //nothing I can do with it...
date(provided by me using NSDate())
How can I make sure if the two objects are correctly connected (latest data with current time) before storing them?
Assuming you are calling a web api, most of the web apis would return cache headers. It is safe to cache the data based on cache header value, that way there wont be any stale cache.
The solution that I came across that works for me is to set the stalenessInterval on the Main Queue NSManagedObjectContext as follows inside of your extension:
objectContext.stalenessInterval = 0.0;
This tells the context in the extension to fetch new data every time and ignore the cache.

Parse: How get all saveEventually tasks

I'm using Parse in my iOS app.
In my app I'm using a lot of saveEventually() functions to store data in Parse without there needing to be an internet connection available.
I know that saveEventually() returns a BFTask object.
It is possible to get all the tasks created in order to check their status in any given moment? Also, could this same technique be used after an app restart?
Thanks!
Parse is using Boltz framework, which in return, you will have a BFTask object upon completion.
Example:
yourPFObject.saveEventually().continueWithBlock {
(task: BFTask!) -> BFTask in
if task.isCancelled() {
// the save was cancelled.
} else if task.error() {
// the save failed.
} else {
// the object was saved successfully.
var object = task.result() as PFObject
}
}
With this in mind, you can check their status by storing the completed task in coredata. In appDelegate applicationDidBecomeActive or didFinishLaunchingWithOptions, you can attempt to retrieve them from coredata depending on your logic.
Of course if you want to just keep track on the status, you can keep in a dictionary and stores in NSUserDefault. It is completely up to your choice and needs.
More example can be found in this link : https://github.com/BoltsFramework/Bolts-iOS

Resources