Parse.com caching with localDataStore enabled - ios

I have caching problems in the iOS-Parse-SDK and I don't know what is wrong. I'm using the local datastore and it runs smoothly. But sometimes when I call a PFCloud function I don't get the current but an old version of an object or at least some fields of it are old (in my case it is a custom object with the field "status"). The strange thing is if I call the same function in the dev console on the Parse site I get the updated object. Is there anything I can do to prevent caching within the app or at least make sure I get the current version of an object?
Example PFCloud call:
[PFCloud callFunctionInBackground:#"importFriends"
withParameters:#{#"phoneNumbers": numbers}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
DDLogDebug(#"Request Send Numbers SUCCESS: %#", result); //result objects are out of date
if (successBlock) successBlock(result);
}else{
DDLogDebug(#"Request Send Numbers FAIL: %#", error);
if (failedBlock) failedBlock(error);
}
}];

It seem like when you use the localdatastore and custom classes for any object you can't overwrite it's setters otherwise parse will never change it again. Even though the setter did exactly what a normal setter would do just a little bit more math afterwards.

Related

Core Data Requests from Today Extension returning 0 results?

I'm working on a simple app, and am looking to include some Core Data functionality from the Today Widget. It's giving me some trouble however, and I'm not sure what it is I'm missing.
Here's what I have done:
I've added the widget target to the Core Data model
I have NOT added my entity subclasses. This opens a can of worms with a bunch of sharedApplication references. I don't need to do much within the widget, so I think it will be much easier to just work with NSManagedObject instances.
In viewDidLoad of the widget I create a persistent container:
NSPersistentContainer *myContainer = [[NSPersistentContainer alloc] initWithName:#"MyDataModelName"];
[myContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
NSLog(#"Unresolved error %#, %#", error, error.userInfo);
abort();
}
}];
So far so good, and no error is returned.
However, no results are returned when I call:
[myContainer.viewContext executeFetchRequest:request error:&error];
Entity Name and predicates don't seem to make a difference. No request error is returned. Identical requests work fine within the app proper.
It would seem that I am maybe initiating an empty container instead of grabbing the correct one? I'm sure this is a simple thing, but I'm at a loss. Any help is appreciated.

Get NSString out of ENNoteContent with Evernote Cloud SDK 2.0

I am new to Evernote SDK development and am using the evernote cloud SDK 2.0 as recommended by Evernote.
However, I am having trouble to get the NSString content out of the ENNoteContent object. I have tried the followings from searching online but none seems to work with the cloud sdk as I guess they are all for the old version of Evernote SDK...
1 Using "convertENMLToHTML" method.
According to this and this, I could call convertENMLToHTML directly on an ENNoteContent object much like this convertENMLToHTML:note.content. However, in the cloud SDK, this resulted in an exception inside ENMLUtility that terminates the app because convertENMLToHTML is expecting an NSString as opposed to ENNoteContent and the first thing this function does is trying to call [enmlContent dataUsingEncoding:NSUTF8StringEncoding]] which caused the exception if enmlContent is a pointer to ENNoteContent but not a pointer to NSString.
2 Attempting to get _emml object out of the ENNoteContent object
This post has a quote of calling [note.content enml] but this again doesn't work with cloud sdk as object enml isn't defined in the interface.
Does anyone know how one can get an NSString out of ENNoteContent? I would expect this to be a very straightforward process but am surprised that I wasn't able to find anything that works for the Cloud SDK.
3 Using generateWebArchiveData method
Per Sash's answer below, I have also attempted to use the generateWebArchiveData method in the example from the cloud sdk. The code I have looks like this:
[[ENSession sharedSession] downloadNote:result.noteRef progress:^(CGFloat progress) {
} completion:^(ENNote *note, NSError *downloadNoteError) {
if (note) {
NSLog(#"%#", note.title);
[note generateWebArchiveData:^(NSData *data) {
NSString* strContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"test content %#", strContent);
}];
} else {
NSLog(#"Error downloading note contents %#", downloadNoteError);
}
}];
However, strContent outputs "null" for a note that I have verified with legitimate content.
As a temporary hack, we added #property (nonatomic, copy) NSString * emml;
in ENNoteContent.h and removed the same line in ENNoteContent.m to get around this for now.
You are close. Technique #1 above is what you want, but as you discovered the enml property is private in the "default" SDK. Import the "advanced" header and you'll have access to note.content.enml. That is a string, and you can send it to convertENMLtoHTML if you prefer an HTML representation.
Do note that there is no "plaintext" string content for an existing note. You'll always see it as markup, and if you want to get rid of the markup, doing so is beyond the scope of the SDK-- how to do that depends very much on what the content you're dealing with looks like.
You should check out their samples included with SDK, seems like
-[ENNote generateWebArchiveData:] will get you HTML NSData in the completion block
https://github.com/evernote/evernote-cloud-sdk-ios/blob/master/Getting_Started.md#downloading-and-displaying-an-existing-note might also help

Parse unpinFromBackground doesn't work

I'm currently using Parse to save details about users in an app. When the ViewController is loaded for the first time, the user data is retrieved from the server. When the user leaves the ViewController, I use the code [user pinInBackground] to save user variables to the local datastore. When the ViewController is reloaded later, the user variables are set using the data from the local datastore. This works correctly in my app.
However, when the user leaves to a specific view controller, on returning to the original ViewController, I wish for the app to access the data from the server instead of the local datastore. To do this, I use the code:
[user fetchFromLocalDatastoreInBackgroundWithBlock:^(PFObject *object, NSError *error) {
[user unpinInBackgroundWithName:#"MAINPIN" block:^(BOOL succeeded, NSError *error) {
[user fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
NSLog([NSString stringWithFormat:#"personalHeaderString is equal to %#", user[pfPersonalHeader]]);
}];
}];
}];
The NSLog shows that the code is definitely run as intended. However, the NSLog also shows that the data is not unpinned, and instead the app continues to use data from the local datastore, rather than fetching the data from the Parse server as fetch is supposed to do. How can I fix this?
All help appreciated.
Is there a reason that you are fetching from the local datastore before you unpin? I'm guessing that when you pin the result initially you give it the name "MAINPIN" so why not use:
PFObject.unpinInBackgroundWithName:("MAINPIN")
and then run a your "live" query after after this?
More info here: https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/unpinInBackgroundWithName:
Andrew

Duplicate NSManagedObjects in Core Data using MagicalRecord and MMRecord

Update 21/7/2014: I have found a workaround for the problem described here. I have created a separate NSManagedObjectContext called downloadContext. The downloaded serialized objects are put in that separate context. I then iterate through the response array and check if the object already exists in the MR_defaultContext (using the primary key).
If it does I update the properties from the downloaded object with the same primary key. It is not possible to simply copy the whole object and thus overwriting all properties of the existing object (because the updated object and the old one are in separate contexts). Instead I copy all properties of the downloaded object into an NSDictionary (dictionaryWithValuesForKeys) and then use setValuesForKeysWithDictionary to overwrite the properties of the old object. The same has to be done with the child objects.
If the object is new, I create a new object and copy the properties in the same way. I will need to check if this solution is efficient enough or if it will occupy too much memory in case there are many objects to download (first sync). The alternative is to create my own serialiser that decides on the basis of primary keys if to update an existing object or to create a new one.
For this issue, please note that I have seen the discussion on the relatedByAttribute key setting. It doesn't help in this case since this might only avoid duplicates of child relationship objects ( I haven't checked if there are duplicates). This is about the parent object duplicates.
In my project, I use AFNetworking, MMRecord and MagicalRecord together as dependencies. The situation is as follows:
I create a response serializer from the AFMMRecordResponseSerializer class which I simply set to my subclass object of the AFHTTPSessionManager. With this session manager I send a GET-request to the server. The response object already contains serialized NSManagedObjects (they are actually a subclass of MMRecord). I have set the MMRecordEntityPrimaryAttributeKey to each of the NSManagedObject's intended primary key. I think that the problem lies with MagicalRecords though, because it simply ignores MMrecord's primary attribute key setting.
After the initial GET-request with the persistent store still empty, there is no modification/creation date predicate. In case there are already persisted objects, the GET-request contains a predicate retrieving only objects after the last mod date from the server. So the updated object is persisted but the older object is not overwritten. When I display the titles of the objects in a table view I can see that the older version and updated version of an object are both displayed. Important: the table view is populated only from core data, not directly from the responseObject (containing the serialized objects).
Below is the most important code.
Thanks for any hints in the right direction
Dominik
-(void) fetchNotesFromServer
{
NSLog(#"fetchNotesFromServer called");
self.sessionManager.responseSerializer = [self getSerializerForEntityName:#"NAS_Notes" AndEndPointPathComponent:#"DBFetchNotes.php?"];
[self.sessionManager GET:[self getFetchNotesExtension] parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject)
{
if (responseObject )
{
NSLog(#"fetch all notes response: %#", responseObject);
self.latestNotes = (NSMutableArray *)responseObject;
if ([self.latestNotes count] > 0)
{
[self saveDownloadedNotesInContext:[NSManagedObjectContext MR_defaultContext]];
}
}
} failure:^(NSURLSessionDataTask *task, NSError *error)
{
NSLog(#"NSerror %#", error);
}];}
-(void) saveDownloadedNotesInContext:(NSManagedObjectContext *)context{
[context MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
[self.delegate didFinishNotesSync:self];
}
else if (error) {
NSLog(#"error saving to persistent store %#", error);
}
}];
}

Can parse cache replace core data

I'm building a simple contact directory with CRUD functions, and I want to have editing functionality offline. My database for the contacts will be stored on a Parse.com backend. Would this be possible using the Parse cache functionality, or would it be better for me to resort to using Core Data?
No, it can't. Parse simply caches the results of your queries. This is extremely limiting and not the same as caching all of your PFObjects. For example, say you ran a query for all of your "contacts" PFObjects. The only thing you would be able to do with the cache is run the exact same query again later (and get the exact same result). You couldn't even query for a subset of that cached data.
Another issue is the cached objects will not be updated with any changes your user is making to your PFObjects. For example, say a user is editing contacts offline and your code is calling saveEventually to save those changes to Parse when possible. When you get a cached query result the user's changes will not be reflected in your PFObjects. The same goes for deleted and added PFObjects I believe. This would make offline use terrible. Here is a thread from the Parse forum that touches on this subject:
https://www.parse.com/questions/does-saving-an-object-supposed-to-update-the-local-cache-of-a-query-on-these-objects
I have seen Parse developers mention improved caching of Parse objects coming at some point in the future. However, this feature does not exist yet and there is no knowing when it will come. In the mean time, if you want to support offline use you have to use Core Data or some other local store. FTASync (which I have never used) is designed to sync Parse objects with Core Data:
https://github.com/itsniper/FTASync
You could also write your own code to sync the two sets of data.
The Parse iOS/OSX SDK provides a local datastore which can be used to store and retrieve PFObjects, even when the network is unavailable. To enable this functionality, add libsqlite3.dylib and call [Parse enableLocalDatastore] before your call to setApplicationId:clientKey:.
As stated in the Parse documentation:
You can store a PFObject in the local datastore by pinning it. Pinning
a PFObject is recursive, just like saving, so any objects that are
pointed to by the one you are pinning will also be pinned. When an
object is pinned, every time you update it by fetching or saving new
data, the copy in the local datastore will be updated automatically.
You don't need to worry about it at all.
PFObject *gameScore = [PFObject objectWithClassName:#"GameScore"];
gameScore[#"score"] = 1337;
gameScore[#"playerName"] = #"Sean Plott";
gameScore[#"cheatMode"] = #NO;
[gameScore pinInBackground];
If you have multiple objects, you can pin them all at once with the
pinAllInBackground convenience method.
[PFObject pinAllInBackground:listOfObjects];
Retrieving an object from the local datastore works just like
retrieving one over the network. The only difference is calling the
fromLocalDatastore method to tell the PFQuery where to look for its
results.
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query fromLocalDatastore];
[[query getObjectInBackgroundWithId:#"xWMyZ4YE"] continueWithBlock:^id(BFTask *task) {
if (task.error) {
// Something went wrong.
return task;
}
// task.result will be your game score
return task;
}];
Any PFQuery can be used with the local datastore just as with the
network. The results will include any object you have pinned that
matches the query. Any unsaved changes you have made to the object
will be considered when evaluating the query. So you can find a local
object that matches, even if it was never returned from the server for
this particular query.
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query fromLocalDatastore];
[query whereKey:#"playerName" equalTo:#"Joe Bob"];
[[query findObjectsInBackground] continueWithBlock:^id(BFTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
return task;
}
NSLog(#"Retrieved %d", task.result.count);
return task;
}];
When you are done with an object and no longer need it to be in the
local datastore, you can simply unpin it.
[gameScore unpinInBackground];
There's also a method to unpin several objects at once.
[PFObject unpinAllInBackground:listOfObjects];
For more information on using Parse's local datastore check the Local Datastore documentation provided for iOS/OSX on parse's website.
No it can not. It's no where near the same, I suggest https://github.com/itsniper/FTASync

Resources