iOS - Parse: findObjectsInBackgroundWithBlock with completion - ios

I'm fetching data using findObjectsInBackgroundWithBlock but I have to use the data when all of it is gathered. I don't know how to implement a callback or anything which would alert me once all the data is retrieved and ready for me to use. Is there any way to get alerted?
Code:
[query findObjectsInBackgroundWithBlock:^(NSArray* array, NSError* error)
{
if(!error)
{
if([array count] > 0)
{
PFObject* relationship = [array objectAtIndex:0];
if([relationship.objectId length] > 0)
{
if([[relationship objectForKey:#"initiatedBy"] isEqualToString:parseID]) // relationship initiated by current user -youLike
{
[youLike addObject:relationship];
NSLog(#"youLike added");
}
else
{
[likeYou addObject:relationship];
NSLog(#"likeYou added");
}
}
else NSLog(#"Custom error when cycling through user relationships: objectId is nil");
}
else
{
NSLog(#"Custom error when cycling through user relationships: Relationship at index %d could not be found in database", i);
}
}
else
{
NSLog(#"Error when cycling through user relationships: %#", error);
}
}

You need to use Blocks
Declare the Block (Here you choose your block types)
typedef void (^YourBlock)(NSArray *array, NSError *error);
Add the block and the findObjectsInBackgroundWithBlock in a method.
- (void) yourMethod:(YourBlock)block {
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error) {
//Do your things here if you want change something.
//For instance: convert types, convert errors.
block(array, error); //Is called when every thing is retrieve.
}
}
Call your method in the Application
SomeClass *someClass = [SomeClass alloc]init];
[[object getScoresFromParse:^(NSArray *array, NSError *error) {
//Everything complete here.
}];

You can use blocks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[query findObjectsInBackgroundWithBlock:^(NSArray* array, NSError* error)
{
if(!error)
{
if([array count] > 0)
{
PFObject* relationship = [array objectAtIndex:0];
if([relationship.objectId length] > 0)
{
// It will be called when Parse finishes.
dispatch_async(dispatch_get_main_queue(), ^{
if([[relationship objectForKey:#"initiatedBy"] isEqualToString:parseID]) // relationship initiated by current user -youLike
{
[youLike addObject:relationship];
NSLog(#"youLike added");
}
else
{
[likeYou addObject:relationship];
NSLog(#"likeYou added");
}
// [...yourcode...]
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Custom error when cycling through user relationships: objectId is nil");
});
}
}
}
}];
}];

Related

Delete PFUser with Cloud Code Parse.com iOS

I succeed to add friend with Cloud Code and Parse.com.
Now I would like to delete friend relation with Cloud Code in didSelectRowAtIndexPath
My error is "attempt to insert nil object from objects[0]'"
But I don't know what parameters I need to configure, I found the cloud code for main.js :
Parse.Cloud.define("removeFriend", function(request, response)
{
// here's how to get the client's user making the request
var user = request.user;
// consider checking that user && request.params.friend are valid
// if not, return response.error("missing user or friend id")
getUser(request.params.friend).then(function(friend) {
// your code prematurely called response.success() here, thereby canceling any further steps
friend.relation("friendsRelation").remove(user);
// return the promise returned by save() so we can chain the promises
return friend.save();
}).then(function(result) {
// only now that the save is finished, we can claim victory
response.success(result);
}, function (error) {
response.error(result);
});
});
// EDIT - the OP once referred to a getUser function that we assume to be something like this:
// return a promise to get a user with userId
function getUser(userId) {
var userQuery = new Parse.Query(Parse.User);
return userQuery.get(userId);
}
Here is my code EditFriends.m :
- (void)viewDidLoad
{
[super viewDidLoad];
PFQuery *query = [PFUser query];
[query orderByAscending:#"name"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
else {
self.allUsers = objects;
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
}];
self.currentUser = [PFUser currentUser];
[self loadFriends];
}
-(void) loadFriends{
self.friendsRelation = [[PFUser currentUser] objectForKey:#"friends"];
PFQuery *query = [self.friendsRelation query];
[query orderByAscending:#"username"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (error) {
NSLog(#"Error %# %#", error, [error userInfo]);
}
else {
self.friends = objects;
[self.tableView reloadData];
}
}];
}
- (BOOL)isFriend:(PFUser *)user {
for(PFUser *friend in self.friends) {
if ([friend.objectId isEqualToString:user.objectId]) {
return YES;
}
}
return NO;
}
CellForRowAtIndexPath :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PFUser *user = [self.allUsers objectAtIndex:indexPath.row];
NSString *name = [[self.allUsers objectAtIndex:indexPath.row] valueForKey:#"username"];
cell.textLabel.text = name;
if ([self isFriend:user]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
didSelectRowAtIndexPath :
PFUser *selected = [self.allUsers objectAtIndex:indexPath.row];
if ([self isFriend:selected]) {
NSLog(#"déjà amis");
// PFObject *friendRequest = [self.friendRequests objectAtIndex:indexPath.row];
[PFCloud callFunctionInBackground:#"removeFriend" withParameters:#{#"friendRequest" : selected.objectId} block:^(id object, NSError *error) {
if (!error) {
//add the fromuser to the currentUsers friends
//save the current user
[self.currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
} else {
}
}];
}
else {
}
}];
}
else{
PFUser *selectedUser = [self.allUsers objectAtIndex:indexPath.row];
//request them
PFObject *friendRequest = [PFObject objectWithClassName:#"FriendRequest"];
friendRequest[#"from"] = self.currentUser;
friendRequest[#"fromUsername"] = [[PFUser currentUser] objectForKey:#"username"];
//selected user is the user at the cell that was selected
friendRequest[#"to"] = selectedUser;
// set the initial status to pending
friendRequest[#"status"] = #"pending";
[friendRequest saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Yay" message:#"Friend request sent" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
} else {
// error occurred
}
}];
}
The iOS code looks okay, and only needs to be sure it sends the correct user objectId.
The cloud code is close, but must be improved a little:
Parse.Cloud.define("removeFriend", function(request, response)
{
// here's how to get the client's user making the request
var user = request.user;
// consider checking that user && request.params.friend are valid
// if not, return response.error("missing user or friend id")
getUser(request.params.friendRequest).then(function(friend) {
// your code prematurely called response.success() here, thereby canceling any further steps
console.log("relation is:" + JSON.stringify(friend.relation("friends")));
friend.relation("friends").remove(user);
// return the promise returned by save() so we can chain the promises
return friend.save();
}).then(function(friend) {
// friendship goes both ways, so remove the friend from user's friends
user.relation("friends").remove(friend);
return user.save();
}).then(function(result) {
// only now that the save is finished, we can claim victory
console.log("relation is:" + JSON.stringify(result.relation("friends")));
response.success(result);
}, function (error) {
response.error(error);
});
});
// EDIT - the OP once referred to a getUser function that we assume to be something like this:
// return a promise to get a user with userId
function getUser(userId) {
var userQuery = new Parse.Query(Parse.User);
return userQuery.get(userId);
}
EDIT - to call:
PFUser *selected = [self.allUsers objectAtIndex:indexPath.row];
if ([self isFriend:selected]) {
NSLog(#"déjà amis");
[PFCloud callFunctionInBackground:#"removeFriend" withParameters:#{#"friendRequest" : selected.objectId} block:^(id object, NSError *error) {
// etc.

Return a query from inside a block

i hope NSlog outside of block can have value.
i don't know how to fix it. i hope loadPuppiesFromJSON can working.
could somebody help me!?THX!!
- (id)init
{
if((self = [super init]))
{
allPuppies=[self loadPuppiesFromJSON];
}
return self;
}
- (NSArray *)loadPuppiesFromJSON
{
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
self.allFishes = objects;
NSLog(#"%#",allFishes);<-----here have some value
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"%#",allFishes);<----here doesn't have value
}
The reason your NSLog(#"%#",allFishes); doesn't have a value is that it is being called before your query is complete. It's a Race Condition, and at run-time here is what the program thinks you want:
Start query
Call NSLog outside the block - no data yet, so it's empty
Call completion block when it is ready - this is when the data is generated so you get a populated NSLog here
Since you also want to have the value return, try this instead - just remove your NSLost from outside the block and call a second function with the return value to continue your logic:
- (void)loadPuppiesFromJSON
{
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
}
self.allFishes = objects;
NSLog(#"%#",allFishes);<-----here have some value
[self processPuppiesArray:objects];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
//No NSLog out here - it won't do anything
}
- (void)processPuppiesArray:(NSArray *)puppiesArray
{
//Continue processing here...
}
The NSLog where you are not getting any value is because block is called in background after it completes your function execution. Thats why before executing you block, it finishes your function execution and during that time allFishes variable is empty/nil. Once the block excution starts and assigns value to allFishes after that this variable gets the value.
If you need to execute some code after allFishes have value create a function and call it after blockexecution finishes like below:
- (NSArray *)loadPuppiesFromJSON
{
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
self.allFishes = objects;
NSLog(#"%#",allFishes);<-----here have some value
[self dataLoadFinished];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"%#",allFishes);<----here doesn't have value
}
-(void) dataLoadFinished {
NSLog(#"%#",self.allFishes);<----here you will have value
}
The reason you aren't retrieving any data is because you are trying to retrieve an Array from a function but never return anything.
When you use the -(NSArray *)loadPuppiesFromJSON you actually have to return an Array which you are not doing.
For example:
-(NSArray *)loadPuppiesFromJson {
NSArray *allFishes;
allFishes = #[#"someinfo1",#"someinfo2",#"someinfo3"];
return allFishes; // this is where your getting your error and need for it to return anything
}
I would suggest butting your code into a void function like so:
-(void)loadPuppiesFromJSON {
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.allFishes = objects;
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"%#",allFishes);<----here has value now
}
And call it in viewDidLoad or viewDidAppear or wherever you need it with:
[self loadPuppiesFromJSON]

Why is my code being ran backwards? Or so it seems?

I have an IBAction that when called, registers a user information with Parse.com... I have only implemented the email and username methods, since i need to make sure that prior to submission to the server i added a counter int, if the counter == 2 it will execute the registration... well to my surprise, when I run the code, it gets ran backwards, so my conditional statement at the bottom is not even reviewed, why is this the case?
This is my code:
- (IBAction)signMeUpButton:(id)sender {
[self.view endEditing:YES];
counter = 0;
user = [PFUser user];
NSString *emailFromTextField = self.emailTF.text;
if ([self isValidEmailAddress:emailFromTextField]) {
[self emailHasBeenTaken:emailFromTextField completion:^(BOOL emailIsTaken, NSError *error) {
if (error) {
// TODO: handle any errors here
return;
}
if (!emailIsTaken) {
emailString = emailFromTextField;
user.email = emailString;
counter++;
NSLog(#"The email is %# the counter is %i", emailString, counter);
}
else {
[self duplicateEmail];
}
}];
}
NSString *usernameFromTextField = self.usernameTF.text;
if (usernameFromTextField.length >= 1) {
[self usernameHasBeenTaken:usernameFromTextField completion:^(BOOL usernameIsTaken, NSError *error) {
if (error) {
return;
}
if (!usernameIsTaken) {
usernameString = usernameFromTextField;
user.username = usernameString;
counter++;
NSLog(#"The username is %# and the counter is %i", usernameString, counter);
}
else {
//
}
}];
}
if (counter == 2) {
NSLog(#"Its working");
}
}
And this is what I get on my console when i run it, as you can see the usernameHasBeenTaken is being called FIRST, even though I have it written second... why is that?
2014-07-17 23:18:12.169 app[28210:60b] in the usernameHasBeenTaken. USERNAME IS NOT EXISTENT
2014-07-17 23:18:12.170 app[28210:60b] The username is sample and the counter is 1
2014-07-17 23:18:15.328 app[28210:60b] in the emailHasBeenTaken, EMAIL IS NOT EXISTENT
2014-07-17 23:18:15.328 app[28210:60b] The email is sample#email.com the counter is 2
These are my 2 methods: usernameHasBeenTaken and emailHasBeenTaken
- (void)emailHasBeenTaken:(NSString *)email completion:(void(^)(BOOL emailIsTaken, NSError *error))completionBlock
{
void (^completionCopy)(BOOL, NSError *) = [completionBlock copy];
PFQuery *query = [PFUser query];
[query whereKey:#"email" equalTo:email];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"in the emailHasbeenTaken ERROR HAS OCCURRED");
if (completionCopy) {
completionCopy(NO, error);
}
return;
}
if (objects.count > 0) {
NSLog(#"in the emailHasbeenTaken EMAIL IS DUPLICATE");
if (completionCopy) {
completionCopy(YES, nil);
}
}
else {
NSLog(#"in the emailHasBeenTaken, EMAIL IS NOT EXISTENT");
if (completionCopy) {
completionCopy(NO, nil);
}
}
}];
}
- (void) usernameHasBeenTaken:(NSString *)username completion:(void(^)(BOOL usernameIsTaken, NSError *error))completionBlock
{
void (^completionCopy)(BOOL, NSError *) = [completionBlock copy];
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:username];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"in the usernameHasBeenTaken ERROR HAS OCCURED");
if (completionCopy) {
completionCopy(NO, error);
}
return;
}
if (objects.count > 0) {
NSLog(#"in the usernameHasBeenTaken USERNAME IS DUPLICATE");
if (completionCopy) {
completionCopy(NO, nil);
}
}
else {
NSLog(#"in the usernameHasBeenTaken. USERNAME IS NOT EXISTENT");
if (completionCopy) {
completionCopy(NO, nil);
}
}
}];
}
Your use of a completion block indicates that you are running them asynchronously. If that is the case then your password condition gets hit immediately after you invoke emailHasBeenTaken:completion:
If you must run async, which you should if you are hitting a web service, you need to nest your calls in the completion blocks.
Edit
It appears that this is the case. The Parse API runs async, to avoid locking the UI thread. You will need to nest your callbacks like I show below.
- (IBAction)signMeUpButton:(id)sender {
[self.view endEditing:YES];
counter = 0;
user = [PFUser user];
NSString *emailFromTextField = self.emailTF.text;
if ([self isValidEmailAddress:emailFromTextField]) {
[self emailHasBeenTaken:emailFromTextField completion:^(BOOL emailIsTaken, NSError *error) {
if (error) {
// TODO: handle any errors here
return;
}
if (!emailIsTaken) {
emailString = emailFromTextField;
user.email = emailString;
counter++;
NSLog(#"The email is %# the counter is %i", emailString, counter);
NSString *usernameFromTextField = self.usernameTF.text;
if (usernameFromTextField.length >= 1) {
[self usernameHasBeenTaken:usernameFromTextField completion:^(BOOL usernameIsTaken, NSError *error) {
if (error) {
return;
}
if (!usernameIsTaken) {
usernameString = usernameFromTextField;
user.username = usernameString;
counter++;
NSLog(#"The username is %# and the counter is %i", usernameString, counter);
}
else {
//
}
if (counter == 2) {
NSLog(#"Its working");
}
}];
}
}
else {
[self duplicateEmail];
}
}];
}
}

Run a piece of code when viewController first appears

I'm using parse to store and retrieve data from and to my iOS app. My code is as follows.
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *item in objects) {
self.postPlace.text = [item objectForKey:#"place"];
}
}
else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
However, on first view, I want the following code to be:
self.postPlace.text = nil;
And then the rest of the time:
self.postPlace.text = [item objectForKey:#"place"];
How can I implement this? I've looked into viewDidLoad and viewDidAppear, but I'm a little stuck.
As I have been in protracted discussion with the authors of the other two (at the time of writing) answers about their suggested use of a static local variable, I will provide my own answer, with what I think is a much better solution.
The use of a static local variable to track if something has been done, or not, will limit the number of instances of the class to one, given there can be only one instance of a static local variable per-process. This might not be an issue for a View Controller, where only one instance might be required, however there are often cases where more than one instance of a view controller will be used and using a static local variable will cause a difficult-to-find bug later in development, especially as it's so inconspicuous.
My suggestion would be to track your behaviour using an instance variable, allowing multiple instances of the class and making it obvious you are doing so, as it appears in the class #interface shouting out its purpose (if named correctly).
Therefore:
#interface MyViewController : UIViewController
{
BOOL _haveSetPostPlaceNil;
}
and in the code (there is no need to initialise it to NO unless you really feel the need), use:
for (PFObject *item in objects) {
if (!_haveSetPostPlaceNil) {
self.postPlace.text = nil;
_haveSetPostPlaceNil = YES;
} else {
self.postPlace.text = [item objectForKey:#"place"];
}
}
Do something like this:
Declare a Instance variable of type BOOL, lets call it isFirst.
In your viewDidLoad make isFirst = FALSE;. Then do this in you viewWillAppear (Remember viewDidLoad called only in first time. But viewWillAppear get called each time you come into this viewController. So plan your code accordingly.).:
if (!isFirst) {
self.postPlace.text = nil;
isFirst = !isFirst;
}
else{
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *item in objects) {
static BOOL flag = YES;
if (flag) {
self.postPlace.text = nil;
} else {
self.postPlace.text = [item objectForKey:#"place"];
flag = NO;
}
}
}
else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
Hope this helps ... :)
I suggest you use static BOOL flag to do this task.
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *item in objects) {
static BOOL flag = YES;
if (flag) {
self.postPlace.text = nil;
} else {
self.postPlace.text = [item objectForKey:#"place"];
flag = NO;
}
}
}
else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];

Challenging Online Database Persistence w/ Core Data?

This is my first time doing any work with database persistence/maintenance online, so I apologize in advance for my sloppy code. My app consists of the user creating a bunch of athletes and having their data save online so they can be accessed from any device. This works fantastically, except the athletes each get saved twice online, and it's making me want to rip my hair out. I've checked my code several hundred times, but I just can't seem to find why athletes are being saved twice on the server, resulting in 2 locally as well. I'm utilizing the Parse.com framework. Am I missing something?
The following method gets called from a pull down to refresh on a table view controller.
- (void)getParseData {
NSLog(#"GET PARSE DATA WAS CALLED");
if(self.syncing != TRUE){
NSLog(#"GET PARSE DATA RAN");
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
PFQuery *query = [PFQuery queryWithClassName:#"Athlete"];
[self populateAthleteArray];
if (self.athleteArray.count == 0) {
NSLog(#"ATHLETE ARRAY IS EMPTY");
// If the athlete array has no objects, download all objects from the database.
[query findObjectsInBackgroundWithBlock: ^(NSArray *objects, NSError *error) {
self.syncing = TRUE;
if (!error) {
self.syncing = FALSE;
for (PFObject * object in objects) {
Athlete *newAthlete = [NSEntityDescription insertNewObjectForEntityForName:#"Athlete" inManagedObjectContext:_managedObjectContext];
newAthlete.first = object[#"first"];
newAthlete.last = object[#"last"];
newAthlete.updatedAt = [dateFormatter stringFromDate:[object updatedAt]];
newAthlete.objectId = [object objectId];
[_managedObjectContext save:nil];
}
self.syncing = FALSE;
}
else {
self.syncing = FALSE;
NSLog(#"Error: %# %#", error, [error userInfo]);
}
if(self.needToUploadArray.count > 0){
[PFObject saveAllInBackground:self.needToUploadArray target:nil selector:#selector(emptyUploadArray)];
}
}];
[self populateAthleteArray];
[self.tableView reloadData];
}
else {
NSLog(#"ATHLETE ARRAY HAS ATHLETES ALREADY");
// Athlete array has athletes already
NSMutableArray *athletesToUpload = [NSMutableArray array];
// Placeholder array for athletes that aren't in the database.
for (Athlete *athlete in athleteArray) {
if (athlete.objectId.length == 0 || athlete.objectId == nil) {
// If the objectId is nil, it wasn't uploaded to the database. Add to placeholder array.
[athletesToUpload addObject:athlete];
}
}
[query findObjectsInBackgroundWithBlock: ^(NSArray *objects, NSError *error) {
self.syncing = TRUE;
if (!error) {
// Downloaded all athletes successfully
self.syncing = FALSE;
BOOL found = FALSE;
[self populateAthleteArray];
NSMutableArray *athletesToDelete = [NSMutableArray array];
for (Athlete * athlete in athleteArray) {
for (PFObject * object in objects) {
// Check to see each local athlete exists in the online database
if ([object.objectId isEqualToString:athlete.objectId]) {
// Athlete was find in the online database
found = TRUE;
break;
}
}
if (found != TRUE) {
NSLog(#"%# was not found online.",athlete.first);
if(athlete.objectId.length > 0){
NSLog(#"%# was deleted online. delete them locally",athlete.first);
[athletesToDelete addObject:athlete];
}
else{
// No athlete in the local database matched any of the athletes online
PFObject *onlineAthlete = [PFObject objectWithClassName:#"Athlete"];
onlineAthlete[#"first"] = athlete.first;
onlineAthlete[#"last"] = athlete.last;
PFFile *imageFile = [PFFile fileWithName:[NSString stringWithFormat:#"%#%#MedicalRelease.jpg", athlete.first, athlete.last] data:athlete.medical_release_image];
onlineAthlete[#"medical_release_image"] = imageFile;
[onlineAthlete saveInBackgroundWithBlock: ^(BOOL succeeded, NSError *error) {
self.syncing = TRUE;
if (succeeded) {
NSLog(#"SAVED SUCCESSFULLY");
self.syncing = FALSE;
PFQuery *query = [PFQuery queryWithClassName:#"Athlete"];
[query orderByDescending:#"createdAt"];
[query getFirstObjectInBackgroundWithBlock: ^(PFObject *object, NSError *error) {
Athlete *athleteToChange = [self findAthlete:athlete.objectId];
[athleteToChange setObjectId:[object objectId]];
[_managedObjectContext save:nil];
}];
}
}];
}
}
found = FALSE;
}
if(athletesToDelete.count > 0){
for(id athlete in athletesToDelete){
NSManagedObject *eventToDelete = athlete;
[_managedObjectContext deleteObject:eventToDelete];
[athleteArray removeObjectAtIndex:[athleteArray indexOfObject:athlete]];
[self.tableView reloadData];
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"there is an error: %#", error);
}
}
}
for (PFObject *object in objects) {
// Loop through every athlete downloaded
for (Athlete * athlete in athleteArray) {
// For every object downloaded, compare it to every athlete in the local database.
if ([object.objectId isEqualToString:athlete.objectId]) {
// If the object's id matches the local athletes id, we found the object
if ([object updatedAt] >= [dateFormatter dateFromString:athlete.updatedAt]) {
// If the object has been updated more recently than the athlete, update the local athlete
Athlete *sameAthlete = [self findAthlete:athlete.objectId];
sameAthlete.first = object[#"first"];
sameAthlete.last = object[#"last"];
sameAthlete.updatedAt = [dateFormatter stringFromDate:[object updatedAt]];
sameAthlete.address = object[#"address"];
sameAthlete.objectId = [object objectId];
[_managedObjectContext save:nil];
}
found = TRUE;
// The athlete was found in the database
break;
}
}
if (found != TRUE) {
// We looped through all the local athletes, the object downloaded isn't in the local database; add them.
Athlete *athlete = [NSEntityDescription insertNewObjectForEntityForName:#"Athlete" inManagedObjectContext:_managedObjectContext];
athlete.first = object[#"first"];
athlete.last = object[#"last"];
athlete.objectId = [object objectId];
athlete.address = object[#"address"];
athlete.updatedAt = [dateFormatter stringFromDate:[object updatedAt]];
[_managedObjectContext save:nil];
}
// Reset flag var
found = FALSE;
}
}
else {
self.syncing = FALSE;
NSLog(#"Error: %# %#", error, [error userInfo]);
}
self.syncing = FALSE;
}];
if (athletesToUpload.count > 0) {
for (Athlete *athlete in athletesToUpload) {
PFObject *upload = [PFObject objectWithClassName:#"Athlete"];
upload[#"first"] = athlete.first;
upload[#"last"] = athlete.last;
PFFile *imageFile = [PFFile fileWithName:[NSString stringWithFormat:#"%#%#MedicalRelease.jpg", athlete.first, athlete.last] data:athlete.medical_release_image];
upload[#"medical_release_image"] = imageFile;
[upload saveInBackgroundWithBlock: ^(BOOL succeeded, NSError *error) {
if (succeeded) {
PFQuery *uploadQuery = [PFQuery queryWithClassName:#"Athlete"];
[uploadQuery orderByDescending:#"createdAt"];
[uploadQuery getFirstObjectInBackgroundWithBlock: ^(PFObject *object, NSError *error) {
[athlete setObjectId:[object objectId]];
}];
}
}];
}
}
[self populateAthleteArray];
[self.tableView reloadData];
}
}
}
In the branch marked by the comment // No athlete in the local database matched any of the athletes online you are creating a new PFObject and saving it to Parse. In my understanding, this should not be correct, since the athlete is not in the local db but online.

Resources