Dependent Async Requests Using CloudKit - ios

I am building an application which uses CloudKit as backend. I have a ShoppingList record and GroceryItem record. A shopping list can have many grocery items. I am using the reverse relationship technique which means grocery items have a reference to the shopping list (parent).
Now, I want to display the number of grocery items in the shopping list. Here is my implementation:
-(void) getAllShoppingLists:(GetAllShoppingListsResult)getAllShoppingLists {
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"ShoppingLists" predicate:[NSPredicate predicateWithValue:YES]];
[_privateDB performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
[self getItemsByShoppingList:shoppingList result:^(NSArray *results, NSError *error) {
shoppingList.noOfGroceryItems = results.count;
}];
}
**getAllShoppingLists(results,error);** THIS RETURNS WITHOUT THE noOfGroceryItems BEING UPDATED
}];
}
What can I do to solve this issue? Is there a better way using CKQuery to simple get the total number of grocery items instead of running a for each on each single Shopping List?

Your method getAllShoppingLists is returning without updating your shopping list because performQuery is an asynchronous method. It's going to perform that request in a separate thread. getAllShoppingLists will thus return immediately. The code inside the performQuery block will get called after it has retrieved those objects.
To test it out, run it with the debugger and put breakpoints inside each block. That way you can see how each one gets called.

You can solve that by making the asynchronous method synchronous. Usually this is done by using semaphores.
For a sample see: How do I wait for an asynchronously dispatched block to finish?
But it would be better to leave it async and to continue whatever you want to do at the point where you set the count. One solution for that is by passing on a code block to the getAllShoppingLists method and execute that block right after setting the count.

Related

Azure Mobile Service Offline Data Sync - The item provided was not valid

I am using Azure Mobile Service as a backend for an iOS app. I have set up everything to work with offline sync which allows me to view, add, or modify data even when there is no network connection. I am now into testing and I run into an error: "The item provided was not valid" when I try to synchronize data.
Here's what I am doing:
I add a new athlete to the syncTableWithName:#"Athlete" with this:
NSDictionary *newItem = #{#"firstname": #"Charles", #"lastname": #"Lambert", #"laterality" : #"Orthodox"};
[self.athletesService addItem:newItem completion:^{
NSLog(#"New athlete added");
}];
Here's the addItem function:
-(void)addItem:(NSDictionary *)item completion:(CompletionBlock)completion
{
// Insert the item into the Athlete table
[self.syncTable insert:item completion:^(NSDictionary *result, NSError *error)
{
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
For now everything is fine and the item is in the syncTable. The problem is when I try to synchronize with the Azure Mobile Service. Here's the syncData function I am calling:
-(void)syncData:(CompletionBlock)completion
{
// push all changes in the sync context, then pull new data
[self.client.syncContext pushWithCompletion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self pullData:completion];
}];
}
The pushWithCompletion gets me the error: "The item provided was not valid." and same for the pullData function that gets called after:
-(void)pullData:(CompletionBlock)completion
{
MSQuery *query = [self.syncTable query];
// Pulls data from the remote server into the local table.
// We're pulling all items and filtering in the view
// query ID is used for incremental sync
[self.syncTable pullWithQuery:query queryId:#"allAthletes" completion:^(NSError *error) {
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
I have tried inserting directly in the MSTable and that works fine. It's really when I am using the MSSyncTable that I run into this error. Although when I insert data manually in my database and that I synchronize my context I can fetch data and display within my UITableView.
Lookin forward to see what you guys think about this. Thanks a lot!
I just edited my question thanks to #phillipv.
When I add an item using NSDictionary just like I did I run into the error "The item provided was not valid". So I tried adding an item by first inserting it to my managedObjectContext and then calling:
NSDictionary *dict = [MSCoreDataStore tableItemFromManagedObject:newAthlete];
I then I get the error when I try to sync: "The item provided did not have a valid id."
I feel like I am experiencing a circle.. :S
#Charley14, you can work around the bug by adding the following handler.
- (void)tableOperation:(nonnull MSTableOperation *)operation onComplete:(nonnull MSSyncItemBlock)completion
{
NSMutableDictionary *rwItem = [NSMutableDictionary dictionaryWithDictionary:operation.item];
// Temporary workaround
[rwItem removeObjectsForKeys:#[ #"relationship1", #"relationship2"]];
operation.item = rwItem;
[operation executeWithCompletion:completion];
}
The tableOperation:onComplete: handler is simply removing keys that correspond to the relationships. You will have to replace 'relationship1', 'relationship2' in the code snippet with names of actual relationships in your application.
Once the bug (https://github.com/Azure/azure-mobile-services/issues/779) is fixed, this workaround can be removed.
This appears to be a bug in the iOS SDK, as the Many to One relationship is not supposed to be exposed in the object given to the operation during a Push call.
Created the following bug with more details on GitHub: https://github.com/Azure/azure-mobile-services/issues/779
The cause of the error message is due to the fact that the relationship is a NSSet on the object, and the NSJSONSerializer throws as it does not know how to convert that to JSON.

What is the right way to recreate object on Parse after deleting it?

Im using Parse for server side. And I have a table view with list of Contacts object from Parse. If user taps on object it saves it to parse and if taps again it deletes it from parse.
For saving I use method:
- (void)addContact:(Contact *)contact withBlock:(void (^)(void))completion {
[contact saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (completion) completion();
}];
}
For deleting use this:
- (void)removeContact:(Contact *)contact withBlock:(void (^)(void))completion {
[contact deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
contact.objectId = nil;
if (completion) completion();
}];
}
I set the objectId to nil because I use this property in table view to see if object is allready on parse of it is just on the phone.
The problem is that if user do steps like: save, delete, save.
Save: the object is created on parse with all the data.
Delete: the object is deleted from parse.
Save: the object is created on parse but without data (just objectId).
Is this the normal procedure? On the phone the object has allways all the data even after deletetion method. So I assume if I run save method on the object with all the data, that it will save it to the parse, even if the same object goes through deletion in the past.
Here's a picture of one empty object and one that is saved correctly with all the data:
What are your experience with this? Enjoy resolving this issue and help making wold a better place :)
Setting the object id to nil as you are is relying on private and undocumented features of the PFObject class. Even if it did work now it isn't guaranteed to always work.
You should either not delete the object and simply set a flag to show that it has been removed / deleted and use that for your logic.
Or, you should actually discard the local object once it's deleted and create a new object with a copy of the old objects values.

iOS Objective-C AFNetworking dataTaskWithRequest:completionHandler: can't retrieve data from inside completition block

Here is my code:
//create array with all the teams
NSMutableArray *leagueTeams = [[NSMutableArray alloc] init];
//send request
[[sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
NSDictionary *responseFromJSONDictionary = (NSDictionary *) responseObject;
//copy the team's attributes into temp variables
NSString *tempTeamIDCode = [responseFromJSONDictionary objectForKey:#"teamCode"];
NSString *tempTeamName = [responseFromJSONDictionary objectForKey:#"teamName"];
NSInteger tempTeamPoints = [(NSNumber *) [responseFromJSONDictionary objectForKey:#"teamPoints"] integerValue];
//use temp variables to create a temporary team
Team *aTeam = [[Team alloc] initWithTeamIdCode:tempTeamIDCode andTeamName:tempTeamName andLeaguePoints:tempTeamPoints];
//add team to array
[leagueTeams addObject:[aTeam copy]];
}]resume];
I am trying to make an app that retrieves JSON data from a server. for now I'm using a static JSON to retrieve entries. I used breakpoints to follow the variable values. The app successfully retrieves the JSON data, it successfully creates the 3 temp variables and successfully creates the team object AND successfully adds the object to the leageTeams mutablearray, while inside the success code block.
BUT, the moment the application leaves the success block, the leagueTeams array disappears. it doesn't exist in memory even as an empty array, like it did before the execution of the success block.
I'm probably doing something very wrong with trying to pass data to an outside variable inside the code block, but all other similar questions had the trouble of not getting the data from the server in time, but in my case the data request is always successful and the JSON response and turning it into an NSDICtionary all work fine....so anyone can help please? Thanks
Okay what's happening is the "dataTaskWithRequest:completionHandler:" method is asynchronous! Once it starts the program execution doesn't wait for it to return value it continues to next line outside of it. So probably the code which you have written below this method is executing first than the code inside the completion handler. So what you can do is trigger a Notification or call some delegate method to run any code after the completion Handler returns the required value.

Save PFObjects from Parse Query

I want to save PFObjects from a Parse query in an NSMutableArray that my class has called listdata. I will later use the listdata array. When I traced through my code, it updated the highScoreObjects array for each object found. But when I try to set the listdata array to the highScoreObjects array, the highScoreObjects array is empty. Is there a way to keep the data after the query ends?
NSMutableArray *highScoreObjects = [[NSMutableArray alloc] initWithCapacity:5];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
[highScoreObjects addObject:object];
NSLog(#"%#", object.objectId);
}
dispatch_async(dispatch_get_main_queue(), ^ {
[self.tableView reloadData];
});
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
self.listData = highScoreObjects;
I also tried keeping the line self.listData = highScoreObjects;
inside the self.listData = highScoreObjects; loop. This didn't make any difference.
It isn't that it isn't set. It's that it isn't set yet. This is because you're using findObjectsInBackgroundWithBlock and the asynchronous process hasn't completed yet.
Move your assignment (self.listData = highScoreObjects;) into the block, just before you dispatch the request to reload the table view.
This is yet another case of not understanding the nature of asynchronous programming.
Consider this situation:
You want to make an egg sandwich. You put the eggs on to boil, and set an alarm for when they're cooked to get them out, peel them, cut them up and add them to your sandwich. While you wait you get the bread and butter it, then wait for the alarm to go off.
Your call to findObjectsInBackgroundWithBlock is putting the eggs on to boil. The block you pass it is the alarm and what you plan to do with the eggs once cooked.
Your code above is akin to putting the eggs on to boil, then straight away trying to use the uncooked/partially-cooked eggs on your sandwich. Makes a big mess.
The solution is to call a method at the end of the block your pass to the method.

How to know now when multiple server call methods with nested loops have all finished

I have multiple methods, each with nested loops and facebook requests. There is an array of X id's and each method loops through each id, makes a request for that id then does stuff with the result data.
I need to be notified when each method has completed... ie, when the method has finished looping through all the id's in the array, making the facebook request for each, received the results and finished its tasks with the resulting data. I can't seem to figure out how to make this happen. here are examples of the methods:
- (void)runLoopForFacebookFriendsContent1 {
for (NSString *fbIdStr in self.fbIdsArr){
FBRequest *fbRequest = [FBRequest requestWithGraphPath:graphPathString parameters:nil HTTPMethod:#"GET"];
[fbRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
//show alert
} else {
//Do stuff with the resulting data
}
}];
}
}
- (void)runLoopForFacebookFriendsContent2 {
for (NSString *fbIdStr in self.fbIdsArr){
FBRequest *fbRequest2 = [FBRequest requestWithGraphPath:graphPathStringNumber2 parameters:nil HTTPMethod:#"GET"];
[fbRequest2 startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
//show alert
} else {
for (PF_FBGraphObject *obj in [result objectForKey:#"data"]){
NSLog(#"facebook result: %#",result);
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setValue:#"type2" forKey:#"ContentType"];
[dict setValue:obj forKey:#"data"];
[self.theFacebookDataArray addObject:dict];
}
}
}];
}
}
I call these methods in viewWillAppear. is there a way to setup some sort of completion handler to put the call for these methods inside? and then post an NSNotification when they are all done?
One option is to take a look at the ReactiveCocoa framework. I find it helps tremendously with this kind of pattern, after you get past a bit of a learning curve.
The following is an example directly from the linked file:
// Perform 2 network operations and log a message to the console when they are
// both completed.
//
// +merge: takes an array of signals and returns a new RACSignal that passes
// through the values of all of the signals and completes when all of the
// signals complete.
//
// -subscribeCompleted: will execute the block when the signal completes.
[[RACSignal
merge:#[ [client fetchUserRepos], [client fetchOrgRepos] ]]
subscribeCompleted:^{
NSLog(#"They're both done!");
}];
You could adapt this to the Facebook SDK fairly easily.
This behavior is expected since you are calling dispatch_group_enter and then instantly calling dispatch_group_leave which means the group has nothing to wait for.
You should call dispatch_group_enter before every block and call dispatch_group_leave at the end of every block.
Check the accepted answer here:
Wait until multiple networking requests have all executed - including their completion blocks
Update:
For the given example, you can call dispatch_group_enter before every call to startWithCompletionHandler:, and call dispatch_group_leave at the end of the completion block:
for (...) {
FBRequest *fbRequest = ...;
dispatch_group_enter(group);
[fbRequest startWithCompletionHandler:^(...) {
...
dispatch_group_leave(group);
}
}
Didn't want to have to answer my own question... don't really like doing that, and was hoping for a more elegant solution... but it doesnt seem to be coming and someone even downvoted the question. So i'm just going to post my solution and close the question out. If you end up with a similar problem and are reading this, I dealt with it by:
creating 2 int's for each method. One int is immediately set with the count of the array being iterated through. at the end of each iteration, I am posting an NSNotification. In the NSNotification handler method, i am ++ incrementing the second int & each time running an if condition, checking to see if they match.
It's a pain to keep track of all these when there are many methods like this happening... So if anyone ever finds a better solution, I'd love to hear it. Thanks to everyone who answered and tried to be helpful, I appreciate it!

Resources