parse.com getting pointer data from currentUser - objective-c

i have a pointer key in PFUser and I'm trying to retrieve the object it's pointing to. I've seen many examples about querying for it but there shouldn't be any need for that, since parse has the fetch method and it's a pointer of PFUser class, so I'm using this:
PFObject *menu = [[[PFUser currentUser] objectForKey:#"menuID"] fetchIfNeeded];
I know my current user has an object being pointed to in that key but i get a null menu object all the time

Wain was correct in saying that you need to fetch the currentUser. However, you have to keep in mind that we're working with multiple threads if you want to use fetchInBackground. To keep in on a single thread, simply use [[PFUser currentUser] fetch], but keep in mind that this can cause hanging or blocking for your user when internet connection is bad. Below is a sample of how to use this more effectively. (Same issue with the fetch vs. fetchInBackground for the menu) We have to fetch the menu as well, since it is a pointer and so the currentUser will only fetch the pointer and not the whole object.
[[PFUser currentUser] fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(!error){
PFObject *menu = object[#"menuID"];
[menu fetch];
// Execute any code that needs the menu here, or store it in your viewController (or other class variable) if you need to save it for later
// Alternately, you could use this:
dispatch_async(dispatch_get_main_queue(), ^{
[menu fetchInBackgroundWithBlock:^(PFObject *fetchedMenu, NSError *menuError) {
if(!menuError){
// Execute any code that needs the menu here, or store it
} else {
NSLog(#"%#", menuError);
}
}];
});
} else {
NSLog(#"%#", error);
}
}];

By default currentUser doesn't have any custom added columns of data populated. You need to fetch the user to download that data and then you can use it locally.
Alternatively your oils us cloud code and save a network request.

Related

Use NSManagedObject's own context property to delete it?

It may be kinda naive but I was wondering if it is correct to use the following statement to delete a managed object from the persistent store of Core Data:
[managedObject.managedObjectContext deleteObject:managedObject];
I ran the above in Xcode debugger - it didn't complain but the object's content was still there. Could it be that context was referenced through the object to be deleted, and thus causing a memory lock preventing deletion of the object?
In regards to your content persisting, you still need to call save: on the context after deleting the object.
I can't answer specifically if you will have an issue by referencing the managedObjectContext in the managedObject as I usually use a 'DataManager' to manage my managedObjectContext. Below is an example of a delete method that I used in one of my dataManagers:
- (void)deleteReport:(Report*)aReport inContext:(NSManagedObjectContext*)context {
if (aReport != nil) {
if (context == nil) {
context = self.managedObjectContext;
}
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
[context deleteObject:aReport];
NSError *error = nil;
[context save:&error];
if (error) {
//NSLog(#"%#", error);
}
}}
EDIT: For clarification, the Report in this method is an instance of NSManagedObject, and the method takes NSManagedObjectContext as a parameter, because the application that it was pulled from supports the use of multiple contexts.

Get facebook pictures for later use

So, I'm trying to get all the facebook pictures I'm tagged so I can see them on the iPad, but I wanted to make this function so I can call it everytime I would need to get the url's. The problem is, after I call this function, the array is nil, because the values I get are inside a block. How do I make an array to store the data I get for later use?
-(NSArray *)getFacebookTaggedPictures
{
__block NSArray *taggedPictures = [[NSArray alloc]init];
[FBRequestConnection startWithGraphPath:#"me/photos" completionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if(!error)
{
taggedPictures = [(NSArray *)[result data]copy];
//NSLog(#"the tagged pictures are: %#",result);
}
}];
return taggedPictures;
}
As you have correctly noticed, the array that you return is empty because your method returns before the response from the API has been received and parsed into taggedPictures inside the completionHandler block.
You must save the array inside that block. I'd recommend changing your method as follows:
-(NSArray *)getFacebookTaggedPicturesWithCompletion:(void (^)(NSArray* photos))completion;
where the calling methods would pass the appropriate completion block to handle the obtained pictures. (i.e. save them to disk, display them, do some processing on them etc.):
void (^loggerBlock)(NSArray* photos) = ^(NSArray *array) {
NSLog(#"obtained photos: %#",[array description]);
//save here instead of logging
};
[self getFacebookTaggedPicturesWithCompletion:loggerBlock];
Hope this helps.

What happens if you try to save a PFObject that has been removed by another user?

In my game I have a situation where the player can download a PFObject, and then make changes to it, however that PFObject on the server might be removed by another player, what would then happen if the original player then does a save command on that object? would it crash because it no longer exists on the server? would it create a new object with the same original data? ideally I want nothing to happen, but if there is an issue, is it possible to check if the PFObject still exists on the server before doing the save?
I have used Parse for a while, and I think the "Save" operation will create a new object.
To prevent this, just like you said to fetch the object see if it still exists:
PFQuery *query = [PFQuery queryWithClassName:#"YourClassName"];
[query getObjectInBackgroundWithId:theObject.objectId block:^(PFObject *object, NSError *error) {
if(object) {
// object exists then do the Save
}
}];

on iOS using Parse, how to save two PFFiles to a PFObject in background

My app creates an object (PFUSER) for each user, and an (PF) object for each event they participate in. This works fine. then i have two files associated with that event. i save the first file to a PFFile, then associate it to the event pfobject. when i use blocks and do this in the background, how can then make sure control continues to do the same for the second file?
I am new to blocks so maybe it would be clearer to me why its not working with callbacks, but it seems the block runs the save in another thread and the current one is abandoned before the next steps are taken.
Of course i'd like to do both of these as "save eventually" to allow offline use.
any guidance / examples you can point me to greatly appreciated.
thanks!
saveEventually doesn't support PFFiles yet; it needs a bit more smarts to handle resuming uploads between restarts. One trick that is already available, however, is that PFObject knows how to save its children, including PFFiles. You can just say:
PFUser *user = PFUser.currentUser;
user[#"icon"] = [PFFile fileWithData:iconData];
user[#"iconThumb"] = [PFFile fileWithData:iconThumbData];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// user will automatically save its files & only call this once the
// entire operation succeeds.
}];
I'm not 100% what you mean because you didn't post any codes, but I'd imagine if you want to associate multiple PFFile to PFObject this is all you have to do:
PFObject *object = [PFQuery getObjectOfClass:#"MyFile" objectId:id];
[object addObject:profilePicture forKey:#"Photo"];
[object addObject:coverPicture forKey:#"PhotoCover"];
[object saveEventually];
From Parse's documentation it seems like saveEventually does what you want:
Saves this object to the server at some unspecified time in the
future, even if Parse is currently inaccessible. Use this when you may
not have a solid network connection, and don’t need to know when the
save completes. If there is some problem with the object such that it
can’t be saved, it will be silently discarded. If the save completes
successfully while the object is still in memory, then callback will
be called.
As currently neither saveEvetually nor saving to the local data store are supported, below is a category of PFObject I am using to at least save offline what can be saved or returning error:
- (void) dr_saveWithCompletionHandler: (void(^)(NSError* error)) completionBlock {
__block BOOL canSaveEventually = YES;
[[self allKeys] enumerateObjectsUsingBlock:^(NSString* key, NSUInteger idx, BOOL *stop) {
id object = self[key];
if ([object isKindOfClass:[PFFile class]]) {
PFFile* file = (PFFile*) object;
if (!file.url || file.isDirty) {
canSaveEventually = NO;
}
}
}];
void (^localCompletionHandler) (BOOL, NSError*) = ^(BOOL succeeded, NSError *error) {
if (succeeded) {
if (completionBlock) completionBlock(nil);
} else {
if (completionBlock) completionBlock(error);
}
};
if (canSaveEventually) {
[self saveEventually:localCompletionHandler];
} else {
[self saveInBackgroundWithBlock:localCompletionHandler];
}
}

App crashes when saving UIManagedDocument

I have an application that first loads some data into an UIManagedDocument, then executes saveToURL:forSaveOperation:completionHandler:. Inside the completionHandler block, it does an update of various elements of this database, and when it's done, it does another saving.
Besides that, the app has 3 buttons that reload the data, re-update the data, and delete one entity of the database, respectively. In every button method, the last instruction is a saving as well.
When I run all this in the simulator, all goes smoothly. But in the device doesn't. It constantly crashes. I have observed that, normally, it crashes when pressing the "delete" button, or when reloading or re-updating the database. And it's always in the saveToURL operation.
In my opinion, the problem comes when there are multiple threads saving the database. As the device executes the code slower, maybe multiple savings come at same time and the app can't handle them correctly. Also, sometimes the delete button doesn't delete the entity, and says that doesn't exist (when it does).
I'm totally puzzled with this, and all this saving operations must be done...In fact, if I remove them, the app behaves even more incoherently.
Any suggestions of what could I do to resolve this problem? Thank you very much!
[Edit] Here I post the problematic code. For first loading the data, I use a helper class, with this two methods in particular:
+ (void)loadDataIntoDatabase:(UIManagedDocument *)database
{
[database.managedObjectContext performBlock:^{
// Read from de plist file and fill the database
[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
[DataHelper completeDataOfDatabase:database];
}];
}
+ (void)completeDataOfDatabase:(UIManagedDocument *)database
{
[database.managedObjectContext performBlock:^{
// Read from another plist file and update some parameters of the already existent data (uses NSFetchRequest and works well)
// [database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
[database updateChangeCount:UIDocumentChangeDone];
}];
}
And in the view, I have 3 action methods, like these:
- (IBAction)deleteButton {
[self.database.managedObjectContext performBlock:^{
NSManagedObject *results = ;// The item to delete
[self.database.managedObjectContext deleteObject:results];
// [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
[self.database updateChangeCount:UIDocumentChangeDone];
}];
}
- (IBAction)reloadExtraDataButton {
[DataHelper loadDataIntoDatabase:self.database];
// [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
[self.database updateChangeCount:UIDocumentChangeDone];
}
- (IBAction)refreshDataButton {
[DataHelper completeDataOfDatabase:self.database];
//[self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
[self.database updateChangeCount:UIDocumentChangeDone];
}
[Edit 2] More code: First of all, the initial view executes viewDidLoad this way:
- (void)viewDidLoad{
[super viewDidLoad];
self.database = [DataHelper openDatabaseAndUseBlock:^{
[self setupFetchedResultsController];
}];
}
This is what the setupFetchedResultsController method looks like:
- (void)setupFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Some entity name"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.database.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
}
Each view of the app (it has tabs) has a different setupFetchedResultsController in order to show the different entities the database contains.
Now, in the helper class, this is the first class method that gets executed, via the viewDidLoad of each view:
+ (UIManagedDocument *)openDatabaseAndUseBlock:(completion_block_t)completionBlock
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Database"];
UIManagedDocument *database = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[database.fileURL path]]) {
[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self loadDataIntoDatabase:database];
completionBlock();
}];
} else if (database.documentState == UIDocumentStateClosed) {
// Existe, pero cerrado -> Abrir
[database openWithCompletionHandler:^(BOOL success) {
[self loadDataIntoDatabase:database];
completionBlock();
}];
} else if (database.documentState == UIDocumentStateNormal) {
[self loadDataIntoDatabase:database];
completionBlock();
}
return database;
}
You didn't really provide much code. The only real clue you gave was that you are using multiple threads.
UIManagedDocument has two ManagedObjectContexts (one specified for the main queue, and the other for a private queue), but they still must each only be accessed from within their own thread.
Thus, you must only use managedDocument.managedObjectContext from within the main thread. If you want to use it from another thread, you have to use either performBlock or performBlockAndWait. Similarly, you can never know you are running on the private thread for the parent context, so if you want to do something specifically to the parent, you must use performBlock*.
Finally, you really should not be calling saveToURL, except when you initially create the database. UIManagedDocument will auto-save (in its own time).
If you want to encourage it to save earlier, you can send it updateChangeCount: UIDocumentChangeDone to tell it that it has changes that need to be saved.
EDIT
You should only call saveToURL when you create the file for the very first time. With UIManagedDocument, there is no need to call it again (and it can actually cause some unintended issues).
Basically, when you create the document DO NOT set your iVar until the completion handler executes. Otherwise, you could be using a document in a partial state. In this case, use a helper, like this, in the completion handler.
- (void)_document:(UIManagedDocument*)doc canBeUsed:(BOOL)canBeUsed
{
dispatch_async(dispatch_get_main_queue(), ^{
if (canBeUsed) {
_document = doc;
// Now, the document is ready.
// Fire off a notification, or notify a delegate, and do whatever you
// want... you really should not use the document until it's ready, but
// as long as you leave it nil until it is ready any access will
// just correctly do nothing.
} else {
_document = nil;
// Do whatever you want if the document can not be used.
// Unfortunately, there is no way to get the actual error unless
// you subclass UIManagedDocument and override handleError
}
}];
}
And to initialize your document, something like...
- (id)initializeDocumentWithFileURL:(NSURL *)url
{
if (!url) {
url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Default_Project_Database"];
}
UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[doc.fileURL path]]) {
// The file does not exist, so we need to create it at the proper URL
[doc saveToURL:doc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self _document:doc canBeUsed:success];
}];
} else if (doc.documentState == UIDocumentStateClosed) {
[doc openWithCompletionHandler:^(BOOL success) {
[self _document:doc canBeUsed:success];
}];
} else {
// You only need this if you allow a UIManagedDocument to be passed
// in to this object -- in which case the code above that initializes
// the <doc> variable will be conditional on what was passed...
BOOL success = doc.documentState == UIDocumentStateNormal;
[self _document:doc canBeUsed:success];
}
}
The "pattern" above is necessary to make sure you do not use the document until it is fully ready for use. Now, that piece of code should be the only time you call saveToURL.
Note that by definition, the document.managedObjectContext is of type NSMainQueueConcurrencyType. Thus, if you know your code is running on the main thread (like all your UI callbacks), you do not have to use performBlock.
However, if you are actually doing loads in the background, consider..
- (void)backgroundLoadDataIntoDocument:(UIManagedDocument*)document
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = document.managedObjectContext;
[moc performBlock:^{
// Do your loading in here, and shove everything into the local MOC.
// If you are loading a lot of stuff from the 'net (or elsewhere),
// consider doing it in strides, so you deliver objects to the document
// a little at a time instead of all at the end.
// When ready to save, call save on this MOC. It will shove the data up
// into the MOC of the document.
NSrror *error = nil;
if ([moc save:&error]) {
// Probably don't have to synchronize calling updateChangeCount, but I do it anyway...
[document.managedObjectContext performBlockAndWait:^{
[document updateChangeCount:UIDocumentChangeDone];
}];
} else {
// Handle error
}
}];
}
Instead of parenting your background MOC to the mainMOC, you can parent it to the parentContext. Loading and then saving into it will put the changes "above" the main MOC. The main MOC will see those changes the next time it does a fetch operation (note the properties of NSFetchRequest).
NOTE: Some people have reported (and it also appears as a note in Erica Sadun's book), that after the very first saveToURL, you need to close, then open to get everything working right.
EDIT
This is getting really long. If you had more points, I'd suggest a chat. Actually, we can't do it through SO, but we could do it via another medium. I'll try to be brief, but please go back and reread what I posted, and pay careful attention because your code is still violating several tenants.
First, in viewDidLoad(), you are directly assigning your document to the result of calling openDatabaseAndUseBlock. The document is not in a usable state at that time. You do not want the document accessible until the completion handlers fire, which will not happen before openDatabaseAndUseBlock() returns.
Second, only call saveToURL the very first time you create your database (inside openDatabaseAndUseBlock()). Do not use it anywhere else.
Third. Register with the notification center to receive all events (just log them). This will greatly assist your debugging, because you can see what's happening.
Fourth, subclass UIManagedDocument, and override the handleError, and see if it is being called... it's the only way you will see the exact NSError if/when it happens.
3/4 are mainly to help you debug, not necessary for your production code.
I have an appointment, so have to stop now. However, address those issues, and here's on