iOS multithreading with Parse and dispatch_async - ios

So I'm trying to load a list of usernames from Parse using a PFRelation query. The idea is for this list to be loaded and then used in the view controller that calls it. (through self.yourFriendArray). The original problem is that when I call this function in the viewDidLoad method of the view controller, the self.yourFriendArray array is nil because the query runs synchronously and does not update it.
So my plan was to use the main thread within this findObjectsInBackgroundWithBlock call, but I'm not getting the desired output yet.
Am I multi-threading incorrectly?
Also, I'm just getting used to asking questions on stack overflow -- if there's something wrong with how I'm asking this one please suggest some updates in the comments! Thank you guys!
[[friendsRelation query] findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for (PFUser *user in objects) {
NSString *username = user.username;
[tempArray addObject:username];
dispatch_async(dispatch_get_main_queue(), ^{
[self.yourFriendArray addObject:username];
});
}
}];

I don't know if I'm understanding well what you are trying to do... Is it that you have a tableView that loads the list of userNames from the yourFriendArray? Then, the only thing you need to do is to reload the tableView when the updated array is ready.
[[friendsRelation query] findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for (PFUser *user in objects) {
NSString *username = user.username;
[self.yourFriendArray addObject:username];
}
[self.tableView reloadData];
}];
Sorry if this is not what you are asking...

Try this:
[[friendsRelation query] findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
for (PFUser *user in objects) {
NSString *username = user.username;
[self.yourFriendArray addObject: username];
}
[self.tableview reloadData];
});
}];

Related

Problems in calling Parse getdataInthebackground in a for loop

Hi I am trying to query some files from my Parse database and I want the files to be sorted according to the updateAt time. I have the following code. The query works and the results are sorted according to my condition, but when I load the files using getDataInBackground and then add to an array. The files are not sorted and they appear to be random in the array.
So My questions are
What can I do to make sure the files in the array are in the same order as the query results?
Any way to check the files/images against the objectID in the completion block of getDataInBackground?
p.s. I don't want to use getData since I don't want it to block the main thread.
Thank you very much in advance
PFQuery *query = [PFQuery queryWithClassName:#"Photo"];
[query orderByDescending:#"updateAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *photoStacks, NSError *error)
{
if (!error) {
// The find succeeded.
for (PFObject *photoImage in photoStacks) {
PFFile *userImageFile = photoImage[#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:imageData];
// need to check object id before adding into the stack to make sure the order is right
[photoImageStacks addObject:image];
if ([photoImageStacks count] == photoStacksCount)
{
[photoPile setArray:photoImageStacks];
}
}
}];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
use breakpoint and trace first photoImageStacks and second after response you should call reload method if you are using tableview or some delegate or fire a notification so that you can update ui accordingly after successful response.

How to dispatch blocks with many arguments on a different thread synchronously?

I need to upload some data to server. Just before doing so, I fetch an entity that this data should be bounded to(simple relationship). If there is no such entity, I want to create it then and continue uploading process. The problem, though, is that if there are too many requests, all requests will end up faster than creation of the entity, thus many entities will be created. The solution is to dispatch the block of code synchronously, but on a different thread. How do I do this?
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{
PFQuery *query = [PFQuery queryWithClassName:#"FileOwner"];
[query whereKey:#"identifier" equalTo:#"ABCDEFG"];
PFObject *fileOwner = [query getFirstObject];
if(fileOwner){
[self continueUploadingData:data completionHandler:completionBlock];
}
else{
PFObject *newFileOwner = [PFObject objectWithClassName:#"FileOwner"];
newPhotoOwner[#"identifier"] = #"ABCDEFG";
[newPhotoOwner saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)[self continueUploadingData:data completionHandler:completionBlock];
}];
}
});
This works, but obviously blocks the main thread. I can't use detachSelector: toTarget: withObject: because I have more than one argument.
The general purpose advice that will help here is to build the atomic functions with methods that take completion blocks (like parse's, maybe like your continueUploading method).
In this case, we need a function that will look for an object, return it if it's found, or create and return it if it's not found, all asynch...
- (void)findOrCreateFileOwnerWithIdentifier:(NSString *)identifier completion:(void (^)(PFObject *, NSError *))completion {
PFQuery *query = [PFQuery queryWithClassName:#"FileOwner"];
[query whereKey:#"identifier" equalTo:identifier];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (object) {
return completion(object, nil);
} else {
PFObject *newFileOwner = [PFObject objectWithClassName:#"FileOwner"];
newFileOwner[#"identifier"] = identifier;
[newFileOwner saveInBackgroundWithBlock:completion];
}
}];
}
Now you can say...
[self findOrCreateFileOwnerWithIdentifier:#"ABCDEFG" completion:^(PFObject *fileOwner, NSError *error) {
if (!error) {
[self continueUploadingData:data completionHandler:completionBlock];
} else {
// handle error... probably pass it to completionBlock
// not shown because the OP code doesn't show how the block is declared
}
}];

Query PFUser not working

I am using this query to find users, it works but it just shows me the first user. I want it to show me the user with the text of an UITextField.
How can I do that ?
(I have a textfield and there I type in a name and then it should show the parsed users with the name)
PFQuery *query = [PFUser query];
NSArray *users = [query findObjects];
userQuerys.text = users[0][#"username"];
Thanks very much
This code will fetch you all the PFUsers in which username is equal to the name parameter:
- (void)searchUsersNamed:(NSString *)name withCompletion:(void (^)(NSArray *users))completionBlock {
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:name];
[query findObjectsInBackgroundWithBlock:^(NSArray *users, NSError *error) {
if (!error) {
// we found users with that username
// run the completion block with the users.
// making sure the completion block exists
if (completionBlock) {
completionBlock(users);
}
} else {
// log details of the failure
NSLog(#"Error: %# %#", error, [error description]);
}
}];
}
An example, if you need to update the UI with the result, for example, a table:
- (void)someMethod {
// we will grab a weak reference of self to perform
// work inside the completion block
__weak ThisViewController *weakSelf = self;
//replace ThisViewController with the correct self class
[self searchUsersNamed:#"Phillipp" withCompletion:^(NSArray *users) {
//perform non-UI related logic here.
//set the found users inside the array used by the
//tableView datasource. again, just an example.
weakSelf.users = users;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//pefrorm any UI updates only
//for example, update a table
[weakSelf.tableView reloadData];
}];
}];
}
A small note: the completionBlock here wont run if there is an error, but it will run even if no users were found, so you gotta treat that (if needed. in this example, it was not needed).
Avoid running non-UI related logic on that mainQueue method, you might lock the Main thread, and that`s bad user experience.

PFRelation won't save on Parse.com

I am having trouble saving a PFRelation I have this code:
//set up the query
PFQuery *query = [PFQuery queryWithClassName:#"messageBank"];
[query whereKey:#"username" equalTo:name];
__weak User *weakSelf = self;
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(error) {
NSLog(#"No such user");
handler(NO, error,NO,NO);
}
else{
[weakSelf.friendsRelation addObject:object];
[weakSelf.friends addObject:object];
//save in the background
[weakSelf.messageBank saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(error) {
NSLog(#"Save error");
}
else {
NSLog(#"no error");
}
}];
handler(YES,nil,NO,NO); //no errors
//so the friend is added to the friends array, all we need to do is reload the table data don't need to init the array again, the relation is also added to the relation item so don't need to init that again
}
}];//end block
My code finds the messageBank object fine but it won't save it to the PFRelation friends. It doesn't even attempt to call [weakSelf.messageBank saveInBackgroundWithBlock.... weakSelf.messageBank is the local PFObject and weakSelf.friends is it's PFRelation. Anyone have any ideas what could be going wrong here? If I have a PFRelation in class A is it okay to have pointers in that relation to other objects in class A? Does it need to be in a different class? Any help would be much appreciated!!!
Here's a cleaned up version of the code that fetches an object and adds to its relation, and saves it...
PFQuery *query = [PFQuery queryWithClassName:#"messageBank"]; // by convention class names should be capital MessageBank, but using yours
[query whereKey:#"username" equalTo:name]; // better form is self.name assuming it is an attribute of self
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
// see note below about weakSelf
// assume self is a PFObject subclass with two relations
// (and generated setters) called friendsRelation and friends
[self.friendsRelation addObject:object];
[self.friends addObject:object];
// notice we save self here. that's who changed in the two preceding lines
[self saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(!error) {
// success
} else {
// handle error
}
}];
} else {
// handle error
}
}];
Please notice that there's no need to declare a __weak copy of the self pointer (though, there's no harm in it). That idiom is used to avoid a retain cycle between self and the block's owner. You need it only when self is the blocks owner (directly or indirectly). This isn't the case with parse's completion blocks.

Parse -getObjectInBackgroundWithId not saving on the database

I have a code snippet that will update information in the Parse database. I set up an action so that when the information is saved, it gets updated in the background. The function is being executed, but it's not saving anything on the database. its going through the function, but no changes are being saved. The changed values are inside a text field and a switch.
- (IBAction)save:(id)sender {
NSLog(#"classnameinsave %#", self.productTitleField.text);
PFQuery *query = [PFQuery queryWithClassName:self.className];
[query getObjectInBackgroundWithId:self.productId block:^(PFObject *object, NSError *error) {
NSLog(#"inside getObjectsInBackgroundWithId function");
NSLog(#"priceinsave %#", object[#"price"]);
object[#"title"] = self.productTitleField.text;
object[#"price"] = self.priceField;
object[#"quantity"] = self.quantityField;
object[#"show"] = self.show;
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded){
NSLog(#"Succeeded in Saving");
}else if(error){
NSLog(#"Error with saving changes %#", error);
}
}];
}];
}
Most likely this happened because the PFObject you're looking for doesn't even exist! You forgot to add the if statement for verifying if the object exists. Also, I understand you want to make it as dynamic as possible by having the class name based on a UILabel, but please don't do this, this doesn't make sense at all. Replace your code with this:
PFQuery *query = [PFQuery queryWithClassName:#"ClassName"];
[query getObjectInBackgroundWithId:self.productId block:^(PFObject *object, NSError *error) {
if(object){
//Do everything here :)
}else{
//Display an UIAlertView to the user?
NSLog(error);
}
}];
Note: You should add the if statement, however it's not necessary.
Best regards,

Resources