This question already has answers here:
Obj-C class method results from block
(2 answers)
Closed 8 years ago.
I have a function that fetches list of members using parse query . I have a property of type NSMutableArray which should store the list result named subscribers. I have two blocks in code. One block gets the date and once I have date I call another function which further calls a parse cloud method. Here is the code
-(void) fetchMemberList{
PFQuery *query=[PFQuery queryWithClassName:#"GroupMembers"];
[query fromLocalDatastore];
[query orderByAscending:#"updatedAt"];
[query whereKey:#"iosUserID" equalTo:[PFUser currentUser].objectId];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(error)
{
}
else{
if(objects.count==0) {
_latestTime=[PFUser currentUser].createdAt;
}
else {
PFObject *member=[objects objectAtIndex:0];
_latestTime =member.createdAt;
}
[Data getMemberList:_latestTime successBlock:^(id object) {
NSMutableDictionary *members = (NSMutableDictionary *) object;
NSArray *appUser= [members objectForKey:#"app"];
NSArray *phoneUser=[members objectForKey:#"sms"];
if(appUser.count>0){
for(PFObject * appUs in appUser)
{
appUs[#"iosUserID"]=[PFUser currentUser].objectId;
NSString *obj= [appUs objectForKey:#"name"];
NSString *child= [appUs objectForKey:#"childern_names"];
if(child.length>0)
{
[_subscriber addObject:child];
}
else if(obj.length>0)
{
[_subscriber addObject:obj];
}
}
[PFObject pinAllInBackground:appUser];
}
if(phoneUser.count>0){
for(PFObject * phoneUs in phoneUser)
{
phoneUs[#"iosUserID"]=[PFUser currentUser].objectId;
NSString *obj= [phoneUs objectForKey:#"subscriber"];
NSString *child= [phoneUs objectForKey:#"number"];
if(child.length>0)
{
[_subscriber addObject:child];
}
else if(obj.length>0)
{
[_subscriber addObject:obj];
}
}
[PFObject pinAllInBackground:phoneUser];
}
NSLog(#"%# subscriber in fetch method",_subscriber);
} errorBlock:^(NSError *error) {
}];
}
}];
}
In my viewdidload function :-
- (void)viewDidLoad {
[super viewDidLoad];
_subscriber=[[NSMutableArray alloc]init];
[self fetchMemberList];
NSLog(#"%# in viewdidLoad",_subscriber);
}
So the log in viewdidLoad does not print anything but the one in method gives list of names. I want to store the list in array and put it into tableview. What am I missing here?
The findObjectsInBackgroundWithBlock: block is asynchronous so _subscriber isn't immediately available to print after you call yourfetchMemberList` method. That's why the one within the block in fact gives you the list of names -- because by then the callback is complete.
Related
I could use some help troubleshooting this. I use Parse.com, and do a query to return all objects from one of the Parse classes. I do this when the view Appears using the code below. When I run it, the console log shows there are 8 objects, so I would expect the code to run 8 times, one for each object. However, it only runs 3 times. What am I missing?
-(void) viewWillAppear:(BOOL)animated {
PFQuery *query = [PFQuery queryWithClassName:#"Share"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSLog(#"Objects%lu", (unsigned long)[objects count]);
for (int i=0;i<[objects count];i++)
{
PFObject * obj = [objects objectAtIndex:i];
self.theObject = obj;
//Continue running code for each of the items in PFObject
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
I'm performing a query in conjunction with BFTask and Parse. Within the task is a nested query. I wish to return the task only when the outer query has finished fetching the necessary data to add to complete (including data fetched by the inner query). I have already achieved a solution using the main thread, however I do not want to block the user interface.
+ (BFTask*)theTask {
BFTask *task = [BFTask taskFromExecutor:[BFExecutor defaultExecutor] withBlock:^id{
NSMutableArray *complete = [NSMutableArray new]; //do not return complete until it has been populated by relationObj's
[[QueryLibrary queryA] findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
for(PFObject *obj in objects) {
PFQuery *relationQuery = [obj relationForKey:#"aRelation"].query;
[relationQuery findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
for(PFObject *relationObj in objects) {
//inspect and possibly augment relationObj...
[complete addObject: relationObj];
}
}];
}
}];
return complete;
}];
return task;
}
I've tried to restructure my query (queryA) to include the relation. When trying to include the relation, I get the following errors:
+ (PFQuery *)queryA {
PFQuery *query = [PFQuery queryWithClassName:#"aPFObjectSubclass"];
//include other qualifiers...
[query includeKey:#"aRelation"]; //[Error]: field aRelation cannot be included because it is not a pointer to another object (Code: 102, Version: 1.11.0)
[query whereKeyExists:#"aRelation"]; // [Error]: Unsupported query operator on relation field: aRelation (Code: 102, Version: 1.11.0)
return query;
}
Is it possible in your scenario to call [query includeKey:'aRelation']; on the first query?
That would make it return the aRelation objects without having to run relationQuery in a loop, which would eliminate the problem of doing that on the main thread altogether.
More info: http://blog.parse.com/announcements/queries-for-relational-data/
In .h file declare typedef void(^ completion)(NSMutableArray *);
Implementation of your method should look like this:
+ (BFTask *)theTask:(completion) completion {
BFTask *task = [BFTask taskFromExecutor:[BFExecutor defaultExecutor] withBlock:^id{
NSMutableArray *complete = [NSMutableArray new]; //do not return complete until it has been populated by relationObj's
[[QueryLibrary queryA] findObjectsInBackgroundWithBlock:^(NSArray * _Nullable topObjects, NSError * _Nullable error) {
for(PFObject *obj in topObjects) {
PFQuery *relationQuery = [obj relationForKey:#"aRelation"].query;
[relationQuery findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
for(PFObject *relationObj in objects) {
//inspect and possibly augment relationObj...
[complete addObject: relationObj];
}
if([topObjects lastObject] == obj) {
completion(complete);
}
}];
}
}];
}];
return task;
}
Calling your method:
[myClass theTask:^(NSMutableArray * complete) {
//use your "complete" array
}];
I wonder if this would work:
+ (BFTask *)theTask
{
BFTask *task = [BFTask taskFromExecutor:[BFExecutor executorWithOperationQueue:[NSOperationQueue defaultQueue]] withBlock:^id {
NSArray *objectsWithRelations = [[QueryLibrary queryA] findObjects];
NSMutableArray *subqueries = [NSMutableArray array];
for(PFObject *object in objectsWithRelations) {
[subqueries addObjects:[obj relationForKey:#"aRelation"].query];
}
PFQuery *query = [PFQuery orQueryWithSubqueries:subqueries];
NSArray *complete = [query findObjects];
return complete;
}];
return task;
}
There is a chance that the findObject calls will be synchronous on the thread that the BFTask is dispatched onto, and not dispatched onto the main thread.
In any case, you can reduce the number of finds you do by merging the relationship queries into one OR query.
Update:
Would this allow you to run only part of the task synchronously then:
+ (BFTask *)theTask
{
NSArray *objectsWithRelations = [[QueryLibrary queryA] findObjects];
NSMutableArray *subqueries = [NSMutableArray array];
for(PFObject *object in objectsWithRelations) {
[subqueries addObjects:[obj relationForKey:#"aRelation"].query];
}
PFQuery *query = [PFQuery orQueryWithSubqueries:subqueries];
return [query findObjectsInBackground];
}
I have an NSArray called "malls" that contains a large number of NSDictionaries (each a specific mall) that I uploaded to Parse.com. I want my users to be able to access this information to create map annotations.
I've tried to do this in 2 different ways:
I tried uploading the entire array as a property of a single object:
this is the upload:
in the dataBank.h file:
#property (strong, nonatomic) NSMutableArray* malls;
in the .m file
PFObject *obj = [PFObject objectWithClassName:#"malls"];
obj[#"mallsData"] = self.malls;
[obj saveInBackground];
I try to get the data from parse:
-(NSMutableArray *)createAnnotationsFromParse
{
__block NSMutableArray* data = [[NSMutableArray alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls"];
[query getObjectInBackgroundWithId:#"Eaib9yfTRe" block:^(PFObject *object, NSError *error) {
data = [object objectForKey:#"mallsData"];
annots = [self createAnnotations:data];
}];
return annots;
}
The problem is getObjectInBackground is asynchronous and always returns before getting the data from the server. I tried moving the "return annots" inside the code block but that gives the following error: "incompatible block pointer types".
I uploaded 5 "mall" objects to class "malls2". Each object has 2 properties- name and address:
for(int i = 0; i < 5; i++)
{
PFObject *mallsObj = [PFObject objectWithClassName:#"malls2"];
mallsObj[name] = [[self.malls objectAtIndex:i]objectForKey:name];
mallsObj[address] = [[self.malls objectAtIndex:i]objectForKey:address];
[mallsObj saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)
NSLog(#"yay");
else
NSLog(#"%#", error.description);
}];
}
then I try to get it back:
-(NSMutableArray *)createAnnotationsFromParse
{
__block Annotation* anno = [[Annotation alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(error)
NSLog(#"%#", error.description);
else
{
for(int i = 0; i < [objects count]; i++)
{
//createAnnotationWithTitle is a func in a different class that creates the annotation
anno = [anno createAnnotationWithTitle:[[objects objectAtIndex:i] objectForKey:name] andAddress:[[objects objectAtIndex:i]objectForKey:address]];
}
[annots addObject:anno];
}
}];
return annots;
}
I get 5 objects but they're all empty.
It's a basic misunderstanding about asynchronous methods with block parameters. The trick is to get out of the habit of thinking that code that appears later in a source file runs later. The assumption works in this function:
- (void)regularFunction {
// these NSLogs run top to bottom
NSLog(#"first");
NSLog(#"second");
NSLog(#"third");
}
This will generate logs: first, second, third. Top to bottom, but not in this one:
- (void)functionThatMakesAsynchCall {
// these NSLogs do not run top to bottom
NSLog(#"first");
[someObject doSomeAsynchThing:^{
NSLog(#"second");
}];
NSLog(#"third");
}
That function will generate logs - first, third, second. The "second" NSLog will run well after the "third" one.
So what should you do? Don't try to update the UI with results of a parse call until after it completes, like this:
// declared void because we can't return anything useful
- (void)doSomeParseThing {
// if you change the UI here, change it to say: "we're busy calling parse"
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
// change the UI here, say by setting the datasource to a UITableView
// equal to the objects block parameter
}
}];
// don't bother changing the UI here
// don't bother returning anything here
// we just started the request
}
But what if doSomeParseThing is really a model function, whose only job is to fetch from parse, not to know anything about UI? That's a very reasonable idea. To solve it, you need to build your model method the way parse built their's, with block parameter:
// in MyModel.m
// declared void because we can't return anything useful
+ (void)doSomeParseThing:(void (^)(NSArray *, NSError *))block {
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
block(objects, error);
}];
}
Now your view controller can call, leave the query work to your model and the UI work to the vc:
// change UI to 'busy' here
[MyModel doSomeParseThing:^(NSArray *objects, NSError *error) {
// update UI with objects or error here
}];
Figured it out. It looked like I was getting "empty objects" (can be seen here postimg.org/image/ot7ehn29b ) but once I tried to access data from the objects I saw there was no problem. Basiclly I was tricked by the PFObjects in the array showing "0 objects" and assumed it meant they came back from Parse.com empty. Here's how I checked, just for reference:
PFQuery *query = [PFQuery queryWithClassName:#"malls2"];
NSArray *array = [query findObjects];
NSLog(#"%#", [[array objectAtIndex:0] objectForKey:#"name"]; // I have a string property called "name" in my Parse object.
I am currently using parse to develop an iPhone app and I got this code:
I have a Class called "Event" and has a field called "EventName"
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query selectKeys:#[#"EventName"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %lu scores.", (unsigned long)objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
}
}
}];
This works perfectly, but i get the object's IDs instead of the event names which is what i want.
EDIT :
So far I got this inside the IF statement
self.eventArray = [objects valueForKey:#"EventName"];
for(int i=0; i<10 ; i++){
NSLog(#"%#", eventArray[i]);
}
by doing that the question got solved.
All you have to do is use the objects array that was retuned from finding the objects and pass them into your array by using valueForKey.
So it would be
if (!error) {
self.yourEventNameArray = [objects valueForKey:#"EventName"];
}
I'm trying to initialize a couple properties fetched from parse.com when the view is loaded so I can do calculation with them. For instance, I declare the following in my header file:
TaskViewController.h
#property (nonatomic, assign) int taskTotalCount;
#property (nonatomic, assign) int taskCompletedCount;
#property (nonatomic, assign) int progressCount;
- (void)CountAndSetTotalTask;
- (void)CountAndSetCompletedCount;
- (void)CalculateProgress;
Then in the implementation, assuming all the other initialization are setup properly and they are called in viewdidload, below are the method implementations:
TaskViewController.m
- (void)CountAndSetCompletedCount {
// Query the tasks objects that are marked completed and count them
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"Goal" equalTo:self.tasks];
[query whereKey:#"completed" equalTo:[NSNumber numberWithBool:YES]];
[query countObjectsInBackgroundWithBlock:^(int count, NSError *error) {
if (!error) {
// The count request succeeded. Assign it to taskCompletedCount
self.taskCompletedCount = count;
NSLog(#"total completed tasks for this goal = %d", self.taskCompletedCount);
} else {
NSLog(#"Fail to retrieve task count");
}
}];
}
- (void)CountAndSetTotalTask {
// Count the number of total tasks for this goal
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"Goal" equalTo:self.tasks];
[query countObjectsInBackgroundWithBlock:^(int count, NSError *error) {
if (!error) {
// The count request succeeded. Assign it to taskTotalCount
self.taskTotalCount = count;
NSLog(#"total tasks for this goal = %d", self.taskTotalCount);
} else {
NSLog(#"Fail to retrieve task count");
}
}];
}
- (void)CalculateProgress {
int x = self.taskCompletedCount;
int y = self.taskTotalCount;
NSLog(#"the x value is %d", self.taskCompletedCount);
NSLog(#"the y value is %d", self.taskTotalCount);
if (!y==0) {
self.progressCount = ceil(x/y);
} else {
NSLog(#"one number is 0");
}
NSLog(#"The progress count is = %d", self.progressCount);
}
The issue I am encountering is that the taskTotalCount and taskCompletedCount are set correctly and returns different numbers in the first two methods while the NSLog returns 0 for both x and y. Therefore I'm not sure if the third method somehow got loaded before the two properties are set or it's some other issues. Thank you in advance for any pointers.
Assuming you call these three methods like this:
- (void)viewDidLoad {
[super viewDidLoad];
[self CountAndSetCompletedCount];
[self CountAndSetTotalTask];
[self CalculateProgress];
}
then the problem is that the first two methods return immediately while the calls to Parse occur in the background. This means that CalculateProgress is called long before you get back the results from the calls to Parse.
One solution is to just call CountAndSetCompletedCount from viewDidLoad. In its completion handler you then call CountAndSetTotalTask. In its completion handler you finally call CalculateProgress.