In a iOS app I'm developing I'm using parse.com as my backend. SDK Version is 1.2.20.
The problems come when performing fetches like this:
[PFObject fetchAllIfNeededInBackground:objectsToFetch block:^(NSArray *objects, NSError *error) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.collectionView reloadData];
}];
}];
This block never gets called, with or without error. There is no timeout and there is no way to debug or to know what is going on.
I have also tried this form:
NSOperationQueue * parseQueue = [NSOperationQueue new];
[parseQueue addOperationWithBlock:^{
[PFObject fetchAll:objectsToFetch];
[self.collectionView reloadData];
}];
I set a breakpoint on reload data and it is never hit.
"po objectsToFetch" from the debugger console:
<__NSArrayM 0x11620c710>(
<Object:g06aHOTaLI:(null)> {
},
<Object:XDTcLQCegF:(null)> {
},
<Object:KCIFxCSBUw:(null)> {
},
<Object:g06aHOTaLI:(null)> {
},
<Object:0PjRyl9cC4:(null)> {
},
<Object:WjYY01c931:(null)> {
},
<Object:m9F2Dm8HhD:(null)> {
}
)
Can anyone point me in the right direction to solve this issue?
After a lot of toil, I have discovered that this problem only happens when there are duplicate objects in the array to fetch.
As you can see in the array I posted above there were in fact duplicate objects.
I would like a response from someone parse.com. is this is the expected behavior or is it a bug?
If its the expected behavior, the block should be called with an error and a message saying that duplicate objects are not allowed when performing a batch fetch.
There is nothing about this in the documentation.
If it is a bug, I would be happy to post a bug report.
Related
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.
NSLog(#"pressed");
[_order setObject:_placeChosen forKey:#"place"];
_order[#"place"] = _placeChosen;
//[_order saveEventually];
[_order saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
NSLog(#"saving...");
if(succeeded){
NSLog(#"succeeded dude");
[self performSegueWithIdentifier:#"pickDate" sender:self]; }
else{
NSLog(#"error");
NSLog([error debugDescription]);
}
}];
That's my code. As you can see, I've logged all the possible places and here is the crazy thing: There is no error! The "error" won't show up nor will the debugDescription. "saving..." is not showing up either. however, "pressed" DOES show up. I thought it was faulty network connection but i waited for a while, tried a bunch of times, went to different places and it STILL doesn't work. Is this a bug? Or is there another way to do this?
It turns out that in my prepareForSegue in the previous controller, I had spelled the name of the segue identifier wrong so that the _order wasn't being transferred over thus it was nil. Fixing the spelling fixed the issue. Although, Parse should really give an error saying that the PFObject is nil so it can't be saved.
I have an block to run to get a query set of data from a Azure Database :
[query readWithCompletion:^(NSArray *items, NSInteger totalCount, NSError *error) {
How can i get the *items and put it in a tableview ? As I cannot see this variable out of the block. I have tried to assign an external __ array in the block , but no use.
Has anyone tried to do this ?
thanks
Jason
i think you need some thing like this
[RSSParser parseRSSFeedForRequest:request success:^(NSArray *feedItems) {
self.linkArray=feedItems;//
dispatch_async(dispatch_get_main_queue(), ^{
//3
[self.tableView reloadData];
});
}
failure:^(NSError *error) { }];
The easiest way to see how this should work is to download the Quickstart application from the Windows Azure Portal after you create a Mobile Service. The quickstart is a Todo application that pulls down todo items you have added and displays them in a ListView. When you call your Mobile Service's read method, you specify a callback as seen here:
[query readWithCompletion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
[self logErrorIfNotNil:error];
items = [results mutableCopy];
// Let the caller know that we finished
completion();
}];
In this method, a QSCompletionBlock called completion is called from the readWithCompletion method when it's received a response from your Mobile Service. In the Quickstart, that completion looks like this:
[self.todoService refreshDataOnSuccess:^
{
if (self.useRefreshControl == YES) {
[self.refreshControl endRefreshing];
}
[self.tableView reloadData];
}];
This then triggers the tableview to reload data. There are other methods that are part of the TableViewController class that are necessary to bind the data to the table view though so I'd highly recommend walking through the Quickstart code.
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.
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!