Parse saveEventually saves empty object - ios

When I'm trying to save a PFObject without network connection it is successfully saved locally, whenever I start the app again with network connection, it seems that the object is going to be saved to the server, but all parameters are empty.
I'm doing the following procedure, first I create a PFObject with different values and call saveEventually. During these steps I do not have an internet connection (airplane mode is on), therefore it can't be saved to the server and was saved locally.
PFObject *contact = [PFObject objectWithClassName:#"Contact"];
[contact setObject:[PFUser currentUser] forKey:kRelatedToUserKey];
[contact setObject:self.firstname forKey:kFirstnameKey];
[contact setObject:self.lastname forKey:kLastnameKey];
[contact saveEventually];
Now, I'm closing the app and start it again, I'm loading my contacts like that. I'm getting back the correct object with all the correct value for firstname, lastname, etc.
PFQuery *postQuery = [PFQuery queryWithClassName:#"Contact"];
[postQuery whereKey:#"related_to_user" equalTo:[PFUser currentUser]];
[postQuery fromLocalDatastore];
[postQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// here I get back the correct object from local storage, with all values
}
}];
Now, when I'm switching airplane mode off, it tries to save the object to the server, but all I can see in the backend is an objectId without any values.
Whereas the locally saved object has all the correct values.

So what are you trying to do exactly? Here's how it reads:
Your querying 'Contact' class and once you query it and find an object by it's ID (an object you don't do anything with [PFObject *contact]) then you create a PFObject for a completely different Class?? You could circumvent all that if you simply just want to post a PFObject, but maybe you omitted other code that wasn't relevant to the question? But ok. To answer your question saveEventually works hand-in-hand with local data store, so you shouldn't have any issues, as you can see, it get's called, but your values aren't being saved like the objectID is. The objectID is created autonomously, that's why it gets saved and nothing else. I have literally tried to duplicate your error in every way possible but can't, it's your values, they are returning nil. I even used macros (it looks like thats what your setting your keys as), emulated airplane mode etc. To verify, query your pinned object and see what it returns. Additionally, it's best when you do a callback to try to include an if statement or switch case that explicity defines it respectively for better practice :
{
if (succeeded) {
debt.parseID = newDebt.objectId;
}];
}];
Furthermore, be careful about putting essential tasks in the success block because an important element of saveEventually is that if it doesn't complete before the app is terminated and if the object is still in memory, it will try again, but if the object is no longer in memory it will try again the next run-time, but WITHOUT the success block.
Troubleshoot your property values (self.contact | self.amount | self.incomingDebt) how are you defining these
We've come a long ways from the original post, so in an effort to try and bring it back, the real and only issue here is saveEventually.
Saves this object to the server at some unspecified time in the future, even if Parse is currently inaccessible.
The main intent of saveEventually :
Use this when you may not have a solid network connection, and don’t need to know when the save completes
If more than 10MB of data is waiting to be sent, subsequent calls to will cause old saves to be silently discarded until the connection can be re-established, and the queued objects can be saved.
You have no control over when this gets called. Additionally, saving eventually caches the data on the local disk until it can be successfully uploaded, so pinning the same object is redundant. Saving eventually, if you think about it, is a local datastore of it's own, it's stores them on the local disk until internet is available (local datastore)
You have two ways that could circumvent this. Local datastore is a core-data-esque feature enabling users to forego NSFRC with a simple one-liner code pin/pinInBackground:. You could simply pin the objects and when you know there is internet again unpin and save to your backend. Alternatively you could do it the other way, call reachability immediately, and if there is no internet pin: the object, else saveInBackground: first. Or simply take advantage of their caching policies.
REFERENCES :
saveEventually : API Class Reference
Caching Policies : Documentation

It was a bug on the sdk.(1.6.2)
Submitted here: https://developers.facebook.com/bugs/1554492594835537/

I had a similar problem, I actually found that removing or not calling [Parse enableLocalDatastore]; resulted in saveEventually performing as expected (using Parse 1.6.2). I had assumed [Parse enableLocalDatastore]; would be required for this to work.

This bug is now fixed in Parse version 1.6.3!

Related

Get PARSE objectIds array without retrieving PFObjects themselves

The problem is that I want to get all of the objectIDs and I want to minimize data transfer in-between the server and iOS app. So, let's say I have a dozen of thousands of PFObjects on PARSE, they are updated and deleted and my app needs to update its knowledge about what objects are present on PARSE without using push-notifications (I am handling such a case when user disallows them). I can not just load all of the PFObjects every time my UIViewController presents data, retrieve PFObjects' ids and start checking whether my local store (not PARSE Local Datastore, different one) has such ids, since PFObjects themselves are large and there is a plenty of them, but it is ok for me to just load all of the objectIds. So, how to do this, and is it possible at all?
Some method of PFQuery like getAllObjectIds would be very helpful, but there seems to be no such methods.
You can solve the Add and Update situation but for the Delete its easier t use the straightforward solution and periodically request all object.
Here is a solution for the Update/Add object case:
and save the most recentUpdated Date
In the first request to parse set order object by updatedAt:
[query orderByDescending:#"updatedAt"];//orderByDescending
For any futur query set greaterThan:mostRecentUpdatedAt to get only updated and added objects:
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"mostRecentUpdatedAt"]){
NSDate* mostRecentUpdatedAt = [[NSUserDefaults standardUserDefaults] objectForKey:#"mostRecentUpdatedAt"];
[query whereKey:#"updatedAt" greaterThan:mostRecentUpdatedAt];
}
save mostRecentUpdatedAt for futur queries:
if (results.count) {
PFObject* firtObj = [results firstObject];
NSDate* mostRecentUpdatedAt = firtObj.updatedAt;
[[NSUserDefaults standardUserDefaults] setObject:mostRecentUpdatedAt forKey:#"mostRecentUpdatedAt"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
For the Delete case you need a request to count the number or objects and compared to your count. this should be done immediately after checking for add/update since in this case if count mismatch then there is a delete situation. then get all objects again (no magic to be done to identify the deleted object since its already deleted!!).
You can not query the Parse DB for just the objectId's. You would have to pull the entire PFObject and then loop through and store just the id's and discard the rest.
Alternatively, I think you could create a PFRelation from the objects that you are interested in and the user to track just those objects.

updating PFObjects that have been savedEventually but have not been sent to Parse yet

I have a PFObject (called pfObject) I am saving using
[pfObject saveEventually:^(BOOL succeeded, NSError *error) {
if (succeeded) {
if (pfObject.objectId) {
do some stuff
}
There are times when I may want to update this PFObject after it has been saved, but before a network connection may have been established, so that the modified PFObject, not the original version on which I had called saveEventually, get transmitted. I am trying to figure out the most efficient way to do this. I was wondering whether, if I pin it, and then make changes to the pinned object, that when the network connection is established the altered, pinned object might be the one that gets transmitted...then on (succeeded) i could unpin it. I have not yet tried this, but thought I might ask before I did to see if anyone could explain the best approach to this problem.
note: another person asked a similar question, but got no relevant answer

Parse findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) returning 0 objects

I'm using Parse for quite some time now, and suddenly the findObjectsInBackgroundWithBlock has stopped working (returning 0 objects). Even if I use no filters, it still returns 0 objects. I thought maybe the keys were changed but everything looks the same.
The only new difference i've noticed is that when i'm typing findObjectsInBackgroundWithBlock it autocompletes to:
findObjectsInBackgroundWithBlock:(nullable PFArrayResultBlock(nullable )block
I checked around and saw people say it's only a bug on xcode autocorrect side and nothing with Parse services. Parse.com with iOS App and Objective-C code
So you'll probably think, maybe i've got no data in my table, so check out the following example:
I'm trying to find a user from the User table with an email that is equal to 'xxx'. It gives me 0 objects. But when I use
[PFUser logInWithUsernameInBackground:password:block]
I end up with the existing user I logged in with. With the 'xxx' email attribute...
Weird!
Any help?

obtainPermanentIDsForObjects doesn't make passed in object's IDs permanent immediately

I've been wrestling with temporary core data objects within my iOS app for a fair few months now. I use UIManagedDocument which may or may not complicate things a little. The problem I have is when views are trying save URIs for objects during state encoding for restoration I hit problems whenever newly created objects have objectID's that are temporaryIDs.
Previously I'd tried to force save the UIManagedDocument with the following
NSError *saveError=nil;
BOOL bSuccess=[document.managedObjectContext save:&saveError];
[document updateChangeCount:UIDocumentChangeDone];
[document savePresentedItemChangesWithCompletionHandler:^(NSError *errorOrNil)
I thought this was helping fix the temporary objectIDs, it was definitely forcing the saving to store/disk (which shouldn't be necessary when using the more automated UIManagedDocument), but I since discovered that newly created object id's on the document.managedObjectContext were still left with temporary ObjectIDs even after this.
Last night I discovered that the following brute force addition done after the save has occurred in the savePresentedItemChangesWithCompletionHandler's completion handler block seemed to be able to fix up the temporary ObjectIDs that I was still experiencing.
[document.managedObjectContext reset];
This presumably discards the entire context and forces everything to be refreshed with the new permanent ids following the save having completed. I presume this would require at least some form of SQL db being reloaded from disk and so wasn't really an ideal solution.
Finally I discovered that there may be another solution, one that doesn't require brute force saving on the UIManagedDocument, and that's to instead do the following on any newly created NSManagedObject instead
NSError *obtainError=nil;
BOOL bObtainSuccess = [object.managedObjectContext obtainPermanentIDsForObjects:#[object] error:&obtainError];
I do think that this seems to do what's written on the tin. If I test for object's being temporary even just a second or so later then it seems to clear up and find ALL object's processed as permanent. However if I try to test whether they're permanent immediately after calling obtainPermanentIDsForObjects as follows
NSError *obtainError=nil;
BOOL bObtainSuccess = [object.managedObjectContext obtainPermanentIDsForObjects:#[object] error:&obtainError];
assert(![[object objectID] isTemporaryID]);
Then the assert fires, ie. the object still has a temporaryID even though the obtainPermanentIDsForObjects method returned YES, and left obtainError as nil.
This is all done on the main thread, without any context performBlock.. Given the configuration of UIManagedDocument though this should be correct I think.
Has anyone got any thoughts on this? For now hopefully it seems to be ok if i don't check immediately, almost like there's some threading to the operation, which does make me wonder if it should be done on some different thread...
Thanks for your time

Parse.com saveEventually with callback and use NSCache to store data

Parse.com has a saveEventually option that stores data to disc and waits for a network connection to proceed and save it to server.
However, sometimes when a network connection is available and the user quickly switches screens, the new data is not yet saved to the server, causing views to show old data.
For parse.com users, this happens when I run a new query and the saveEventually call is not yet complete.
I would like to implement my own cache system.
I would like to call saveEventually with callback upon completion, so then I would delete the cached data.
This way, I could check first if there's cached data before making network connection.
I think I can do it using NSCache and NSDiscardableContent, I already read the docs, but it would really help me if I could see some simple sample code for creating these objects and storing them on user's device.
Any suggestions?
UPDATE:
OK, this an idea of workaround solution.
Every object that I call saveEventually on I will add to a NSArray and store it in documentsDirectory, then, upon saveEventually completion I will delete the array in documentsDirectory.
If the app closes before completion, I wouldn't get a callback but it wouldn't be a problem because I will perform the query on the server and get the stored NSArray in device as well, I would then create a unique array giving preference to the objects in the documentsDirectory.
I think this could work. My concern is if it will make my app slower.
Any thoughts?
I haven't tried this, but how about setting your query's cache policy, like:
query.cachePolicy = kPFCachePolicyCacheElseNetwork;
If saveEventually is using the regular parse cache, then it should do just what you want.
EDIT - Since the above is apparently not going to work, I can think of several schemes to employ your own cache, but they're all stuck with the fundamental race condition problem: say you save eventually, then query again. Say you've fixed your query code to grab the local object that's being eventually saved.
What if you modify that cached object and save it eventually (again)? Do we know that parse will correctly serialize the two saves?
If you're not worried about this (maybe the second query never generates a save, or you're willing to roll the dice on the race condition), then you have a lot of choices. Let's take the straight-forward one:
Create a singleton with an NSCache property. Give these methods which are in it's public interface...
// call this instead of saveEventually
- (void)cacheThenSaveEventually:(NSArray *)pfObjects {
for (PFObject *object in pfObjects) {
[self.cache setObject:object forKey:[object objectId]];
[object saveEventually:^(BOOL success) {
if (success) [self.cache removeObjectForKey:[object objectId]];
}];
}
}
// call this with the result of a query that might have been in a race with saveEventually
- (NSArray *)freshenUpResults:(NSArray *)pfObjects {
NSMutableArray *fresherObjects = [NSMutableArray array];
for (PFObject *object in pfObjects) {
PFObject *fresher = [self.cache objectForKey:[object objectId]];
[fresherObjects addObject:(fresher)? fresher : object];
}
return [NSArray arrayWithArray:fresherObjects];
}

Resources