Optimize query for Mutual Friends using Parse - ios

Currently, I am attempting to optimize my getMutualFriends method. When I open my 'Friends' view controller, I execute the getMutualFriends method for every friend the user currently has... Which is NOT optimal...but was the easiest solution...
Heres what I did:
[CBUtility queryForFriends:[PFUser currentUser] block:^(NSArray *friends, NSError *error) {
[self.friendsActivityIndicator stopAnimating];
if (error) {
[[[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil cancelButtonTitle:#"Okay"
otherButtonTitles:nil]
show];
return;
}
if ([friends count] == 0 || !friends) {
[self.friendsTable addSubview:self.friendsEmptyView];
return;
}
self.friends = [NSMutableArray arrayWithArray:friends];
[self.friendsTable reloadData];
[self.friendsEmptyView removeFromSuperview];
int i = 0;
//
// THIS IS THE PART THAT SUCKS!!!
//
for (PFObject * friendObject in self.friends) {
[CBUtility queryForMutualFriends:[friendObject objectForKey:kCBFriendToUserKey] block:^(int mutualFriends, NSError *error) {
if (error) {
[[[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil] show];
return;
}
CBFriendsCell *cell = (CBFriendsCell *)[self.friendsTable cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[cell setMutualFriends:mutualFriends];
}];
i++;
}
}];
And heres what +(void)queryForMutualFriends looks like:
+ (void)queryForMutualFriends:(PFUser *)user block:(void (^)(int number, NSError *error))completionBlock
{
PFQuery *usersFriends = [PFQuery queryWithClassName:kCBFriendClassKey];
[usersFriends whereKey:kCBFriendFromUserKey equalTo:user];
[usersFriends whereKey:kCBFriendStatusKey equalTo:kCBFriendStatusFriendKey];
PFQuery *currentUsersFriends = [PFQuery queryWithClassName:kCBFriendClassKey];
[currentUsersFriends whereKey:kCBFriendFromUserKey equalTo:[PFUser currentUser]];
[currentUsersFriends whereKey:kCBFriendStatusKey equalTo:kCBFriendStatusFriendKey];
[currentUsersFriends whereKey:kCBFriendToUserKey matchesKey:kCBFriendToUserKey inQuery:usersFriends];
[currentUsersFriends countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (!error) {
completionBlock(number, error);
return;
}
completionBlock(-1, error);
}];
}
So instead of running the loop and passing individual PFUser objects into the getMutualFriends method, I'd like to pass an array of friends into the method and return an array of dictionary objects whose keys are 'user' and 'count' with their respective values (e.g. #[#{#"user":somePFUser, #"count":5}, #{#"user":anotherPFUser, #"count":20}];
I mean, this works fine at the moment but takes up way too much API requests...
Anyone got ideas with how to setup the PFQuery?
EDIT:
Here was a link to a SQL query that solves the same problem

No apparently, you cannot... But you can limit the amount of times you query to the server by instead of querying for mutual friends when you retrieve the mutual friends like I did, you instead cache the results into memory...
I solved this issue by making the query in cellForIndexPath when setting a cells attributes. When the cell is loaded, I check cache first to see if the query has already been made, if it has then I get the cache data... If it hasn't then I make a query to the servers... Only issue I see is that it doesn't update... I figure I can clear cache every minute or so, so the user gets updated automatically instead of pressing a reload button.

Related

Changing field in PFUser from other PFUser, Parse iOS

I am attempting to change the value of another PFUser field, from another unauthenticated PFUser in Parse, however I cant seem to do so. I am attempting to increase the number of 'hours' of one user from another user. Here is how I am attempting to do so:
PFUser *currentUser = [PFUser currentUser];
PFACL *ACL = [PFACL ACLWithUser:[PFUser currentUser]];
[ACL setPublicReadAccess:YES];
PFUser *selectedUser = [self.formValues objectForKey:#"user"];
NSLog(#"User ID: %#", selectedUser.objectId);
PFObject *volunteerSheet = [PFObject objectWithClassName:#"VolunteerSheet"];
volunteerSheet[#"userID"] = selectedUser.objectId;
volunteerSheet[#"fromID"] = currentUser.objectId;
volunteerSheet[#"volunteerTitle"] = [self.formValues objectForKey:#"title"];
volunteerSheet[#"location"] = [self.formValues objectForKey:#"location"];
volunteerSheet[#"volunteerHours"] = [self.formValues objectForKey:#"hours"];
volunteerSheet[#"volunteerDescription"] = [self.formValues objectForKey:#"description"];
volunteerSheet.ACL = ACL;
[volunteerSheet saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// The object has been saved.
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
// Retrieve the object by id
[query getObjectInBackgroundWithId:selectedUser.objectId
block:^(PFObject *user, NSError *error) {
[user incrementKey:#"volunteerHours" byAmount:(NSNumber*)[self.formValues objectForKey:#"hours"]];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(#"Succeeded");
}else{
NSLog(error.description);
}
}];
}];
NSLog(#"Saved");
[self dismissViewControllerAnimated:YES completion:^{
UIAlertView * alert =[[UIAlertView alloc ] initWithTitle:#"Success!"
message:#"Hours Sent succesfully."
delegate:self
cancelButtonTitle:nil
otherButtonTitles: nil];
[alert addButtonWithTitle:#"Okay"];
[alert show];
}];
} else {
// There was a problem, check error.description
NSLog(#"Error: %#",error.description);
}
}];
Users automatically have security put in place that disallow modifying another user (clps or alcs). Maybe consider moving to cloud code and calling use master key?
Users have ACLs set up to only allow themselves to change their values. To get around this, you either have to change the ACL every time you create a user to allow any user to have write permissions for them (VERY RISKY AND NOT SECURE AT ALL), or you need to call a cloud function that calls Parse.Cloud.useMasterKey() , which overrides all permissions, and will enable you to make the changes. Obviously, you should still be doing some sort of security check to make sure that these changes are allowed / proper changes before putting them through.

Comparing an NSString to an NSArray from Parse.com (Obj-c)

I'm trying to compare an NSString to an NSArray from parse with an if statement. Here's what I tried:
-(void)queryParseMethod {
//PFQuery *query = [PFQuery queryWithClassName:classNameString];
PFQuery *query = [PFQuery queryWithClassName:#"SaveClass2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
_barcodeArray = [[NSArray alloc]initWithArray:objects];
//[_tableView reloadData];
}else{
NSLOG(#"ERROR");
}
}];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self queryParseMethod];
NSIndexPath *indexPath;
int barcodInt = indexPath.row %1000000;
PFObject *barcodeObject = [_barcodeArray objectAtIndex:barcodInt];
NSArray *secondBarcodeArray;
secondBarcodeArray = [barcodeObject objectForKey:#"RNG1"];
if ([[secondBarcodeArray objectAtIndex:barcodInt] isEqualToString:_label.text]){
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"FOUND" message:#"THE BARCODE IS CORRECT!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
}else{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"NOT FOUND" message:#"THE BARCODE IS INCORRECT!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
};
}
But it always displays the "NOT FOUND" alert View and I'm sure that the classes are right and the columns are also right but it wont work. Any help will be appreciated.
As long as your _barcodeArray is assigned in background thread (where, I believe runs findObjectsInBackgroundWithBlock:, at point where You're trying to get object barcodeObject from _barcodeArray latter is empty (it can be or can be not). You have classic race condition. If you really need to run this find objects block in background, you should consider using some sync mechanism - like dispatch_semaphore. Or, as I presume, you don't need to call findObjectsInBackgroundWithBlock: and use its synchronous counterpart instead, if present.
The blocks are asynchronous so you can't know when block will run on main thread.
I read your code and I found that you are fetching object asynchronously. It means execution wont wait for that method findObjectsInBackgroundWithBlock: to complete.
So the solution is use the synchronous versions of this methods like...
- (NSArray *)findObjects;
- (NSArray *)findObjects:(NSError **)error;
the above method stops the execution further util the findObject: method not get executed.
Use this
-(void)queryParseMethod {
PFQuery *query = [PFQuery queryWithClassName:#"SaveClass2"];
NSError *error = nil;
[query findObjects:&error] {
if (!error) {
_barcodeArray = [[NSArray alloc]initWithArray:objects];
} else {
//error
}
}
I hope this will work for you...good luck...!! :)

Querying multiple values through parse

so my application loads in a user's contacts and stores their phone numbers but I want to query these phone numbers against ones stored in Parse to determine whether any of their contacts are using the application.
So, I know how to work through every phone number as a single query by using something like the code below inside a for loop,
NSString *phoneNumber = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phoneNumbers, i));
//parse query for any matches to the phone number
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"phoneNumber" equalTo:phonenumberfieldfriend.text];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d users.", objects.count);
// Do something with the found objects
if (objects.count == 0) {
//uialert letting the user know that no phone number matches the query
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No User"
message:#"No user matches this phone number"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
phonenumberfieldfriend.text = #"";
}
//if there is only one number matching the query
if (objects.count ==1) {
for (PFObject *object in objects) {
NSLog(#"%#", objects);
usernamefriend =[ object objectForKey:#"username"];
numberfriend = [object objectForKey:#"phoneNumber"];
firstnamefriend = [object objectForKey:#"firstName"];
lastnamefriend = [object objectForKey:#"lastName"];
emailfriend = [object objectForKey:#"email"];
add.hidden=true;
phonenumberfieldfriend.hidden=true;
confirmuser.hidden=false;
NSLog(#"one user entered %#",usernamefriend);
}
}
//if there is more than one phonenumber matching the query as
//the user to input the friends username
//instead
if (objects.count>1) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"More than one user!"
message:#"More than one user with this number please enter a username instead!"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
phonenumberfriend.text=#"Please enter a username";
add.hidden=true;
adduser.hidden=false;
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];}
but I would like to know whether it is possible to search all the contacts phone numbers with a single query?
You could do this numerous ways, provided I understand what your vision is:
One way is simply preform the query and simply do :
query getFirstObjectInBackground:
https://parse.com/docs/ios/api/Classes/PFQuery.html#//api/name/getFirstObjectInBackground
this narrows it down to only one phone number that matches your query parameters.
or getObjectInBackground:
So in other words just delete findObjects and replace with getFirstObjectInBackground and the first result that matches will be returned

Code stops when trying to save Parse.com object information to NSStrings

so I am making an app that searches for friends locations saved on parse.com. Before I begin, here is the method in question.
- (void)saveFriendLocationInfo {
PFQuery *query = [PFQuery queryWithClassName:#"Locations"];
[query orderByDescending:#"createdAt"];
[query whereKey:#"userString" equalTo:currentFriend];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *parseCurrentFriend, NSError *error) {
if (!parseCurrentFriend) {
NSLog(#"The getFirstObject request failed.");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Uh oh..."
message:#"Looks like that user doesnt exist. Might want to double check that."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[self.navigationController popToRootViewControllerAnimated:YES];
[alertView show];
} else {
NSLog(#"ObjectID = %#", parseCurrentFriend.objectId);
parseCurrentFriend[#"title"] = friendTitle;
NSLog(#"Saved Title");
parseCurrentFriend[#"description"] = friendDescription;
NSLog(#"Saved Description");
parseCurrentFriend[#"latitude"] = friendLatitude;
NSLog(#"Saved Latitude");
parseCurrentFriend[#"longitude"] = friendLongitude;
NSLog(#"Saved Longitude");
}
}];
}
So this code all works fine at retrieving the required object. It reaches the NSLog line in the "else" statement where it is supposed to display the object ID. This displays the correct object id for the object that I am trying to receive. Now heres the weird part. The code seems to stop there. Nothing happens when trying to save the properties (title, description, lat, long) to the respective NSStrings; none of those other NSLog's below that are executed. But the weird thing is that there are no errors and the app keeps running as if that code below the first NSLog wasn't even there. Any insight into why this isn't working.
One problem you have is that you never save the object. After you modify all of the values, you need to save the PFObject.
[parseCurrentFriend saveInBackground];
Edit: Since you are trying to extract the values, then you'd need to do it like below.
friendTitle = parseCurrentFriend[#"title"];
friendDescription = parseCurrentFriend[#"description"];
friendLatitude = parseCurrentFriend[#"latitude"];
friendLongitude = parseCurrentFriend[#"longitude"];

Parse.com: Sort by number of comments

I'm creating a UITableView with posts. If one of the tableview's rows is tapped, a detail page is shown where comments can be made to the post. I'm using the Parse.com framework to realize this.
I save the comments like this in the detailview:
PFObject *comment = [PFObject objectWithClassName:#"Comment"];
comment[#"content"] = _textViewComment.text;
comment[#"post"] = _object;
comment[#"user"] = currentUser;
[comment saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(!error){
UIAlertView* alertSave = [[UIAlertView alloc]initWithTitle:#"Saved" message:#"Your comment is saved" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertSave show];
[self setToDefault:_textViewComment];
}else{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Warning" message:#"Comment not saved" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}];
where _object is the current post.
What I want to do now is sort the first tableview by number of comments. But I'm only making a relation from the comment to the post and not the other way around.
Is there a way to query the posts and sort them by number of comments?
To do this, you should relate comments to posts via an array on Post.
Add a "comments" col of type array on Post. When you fetch the post, use includeKey: on the #"comments" key. Then change your update code to add a comment like this...
// in your code, I think "_object" is a post. calling it "post" here instead...
NSMutableArray *comments = [[post objectForKey:#"comments"] mutableCopy];
[comments addObject:comment];
[post setObject:comments forKey:#comments"];
// then save the post object. parse will save the related comment.
[post saveInBackgroundWithBlock ...
Double check everything in the data browser. Then, after the next fetch of posts, you can sort by comment count...
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error) {
if (!error) {
NSComparator comparator = ^(Post *postA, Post *postB) {
NSNumber *countA = #([postA objectForKey:#"comments"].count);
NSNumber *countB = #([postB objectForKey:#"comments"].count);
return [countA compare:countB];
};
NSArray *sortedArray = [array sortedArrayUsingComparator:comparator];
// use sortedArray as your tableview datasource
}
}];

Resources