Search code examples
iosobjective-cparse-platformnspredicateback4app

Best way to fetch child and parent objects with Parse Server in iOS


I'm trying out the "Sample Blog App" on Parse Server for iOS and cannot figure out what is the smartes way to fetch all child objects of another class (together with the parent objects).

The "Sample Blog App" (which creates automatically when you create a new account) contains the classes Comment and Post. The Comment class contains a relation to the Post class as shown below (from the dashboard), but there is no relation in the opposite direction.

enter image description here

Now, I want to fetch all posts and all the comments related to each post. The code below does that, but I'm assuming there must be a smarter way...? If you know how, please share. Thanks in advance!

- (void)fetchPosts {

    NSString *commentsKey = @"comments";
    NSString *postKey = @"post";

    PFQuery *query = [PFQuery queryWithClassName:@"Comment"];
    [query includeKey:postKey];
    [query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {

        if (error == nil) {

            NSMutableArray *array = [[NSMutableArray alloc]init];

            for (PFObject *comment in objects) {

                PFObject *post = [comment objectForKey:postKey];
                NSDictionary *existingPostDict = [[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K = %@", @"post.objectId", post.objectId]] firstObject];

                if (existingPostDict) {
                    // update comments
                    NSArray *comments = [[existingPostDict objectForKey:commentsKey] arrayByAddingObject:comment];

                    // create new dictionary and replace the old one
                    NSDictionary *newPostDict = [[NSDictionary alloc]initWithObjectsAndKeys:[existingPostDict objectForKey:postKey], postKey, comments, commentsKey, nil];
                    [array replaceObjectAtIndex:[array indexOfObject:existingPostDict] withObject:newPostDict];
                }
                else {
                    // first post, create a new dict
                    NSDictionary *newPostDict = [[NSDictionary alloc]initWithObjectsAndKeys:post, postKey, @[comment], commentsKey, nil];
                    [array addObject:newPostDict];
                }
            }
            self.posts = array; // assuming: @property (nonatomic, strong) NSArray *posts; 
        }
        else {
            NSLog(@"Error fetching posts: %@", error.localizedDescription);
        }
    }];
}

Solution

  • This is how I did it, using findObjectsInBackground together with continueWithSuccessBlock: methods (for better error handling one can choose continueWithBlock: instead):

    - (void)fetchPosts {
        /**
         create "post" and "comment" queries and use a BFTask-method from 
         Bolts.framework to chain downloading tasks together (bundled with Parse SDK)
         */
        NSMutableArray *posts = [NSMutableArray new];
        PFQuery *postQuery = [PFQuery queryWithClassName:@"Post"];
        [[[postQuery findObjectsInBackground] continueWithSuccessBlock:^id(BFTask * task) {
    
            [posts addObjectsFromArray:task.result];
            PFQuery *commentsQuery = [PFQuery queryWithClassName:@"Comment"];
            return [commentsQuery findObjectsInBackground];
        }] continueWithSuccessBlock:^id(BFTask * task) {
            /**
             loop through posts and filter out comments with the same objectId in post,
             then create a dictionary with post and related comments. done! :)
             */
            NSMutableArray *postsAndComments = [NSMutableArray new];
            for (PFObject *post in posts) {
                NSArray *comments = [task.result filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K == %@", @"post.objectId", post.objectId]];
                [postsAndComments addObject:@{@"post":post, @"comments":comments}];
            }
            /**
             note: BFTask-blocks not running in main thread!
            */
            dispatch_async(dispatch_get_main_queue(), ^{
                self.posts = postsAndComments;  // assuming: @property (nonatomic, strong) NSArray *posts;
            });
            return nil;
        }];
    }