嵌套查询块将数据加载到 UITableView

Nested Query Blocks to load data into UITableView

我有一个带有 tableView 作为子视图的 UIViewController。 table 视图将包含 3 个部分,我使用问题 的答案来完成此操作,但数据未加载到 table 中。我在 viewWillAppear 的后台执行我的查询。我担心这是由于嵌套查询块造成的。我怎样才能解决这个问题?这是我的代码:

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];


    PFQuery *gameQuery = [PFQuery queryWithClassName:@"Game"];
    [gameQuery whereKey:@"players" equalTo:[PFUser currentUser]];
    [gameQuery orderByDescending:@"createdAt"];

    [gameQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){

        self.myTurn = [NSMutableArray array];
        self.theirTurn = [NSMutableArray array];
        self.gameOver = [NSMutableArray array];
        self.allGames = [NSArray array];

        for(PFObject *object in objects)
        {
            if([object objectForKey:@"isOver"] == [NSNumber numberWithBool:YES])
            {
                [self.gameOver addObject:object];
            }

            else
            {

                PFRelation *relation = [object relationForKey:@"whoseTurn"];
                PFQuery *relQuery = [relation query];
                [relQuery findObjectsInBackgroundWithBlock:^(NSArray *userObjects, NSError *error1){


                    NSMutableArray *arr = [NSMutableArray array];
                    for(PFUser *user in userObjects)
                    {
                        [arr addObject:user.objectId];
                    }

                    if([arr containsObject:[PFUser currentUser].objectId])
                    {
                       [self.myTurn addObject:object];
                    }

                    else
                        [self.theirTurn addObject:object];

                }];
            }

        }

        self.allGames = [NSArray arrayWithObjects:self.myTurn, self.theirTurn, self.gameOver, nil];
        [self.tableView reloadData];

    }];

}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.allGames count];
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return [[self.allGames objectAtIndex:section] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *MyIdentifier = @"MyIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:MyIdentifier];
    }

    PFObject *object = self.allGames[indexPath.section][indexPath.row];
    cell.textLabel.text = [object objectForKey:@"numPlayers"];

    return cell;
}

以下是您可以尝试执行的操作:

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    PFQuery *gameQuery = [PFQuery queryWithClassName:@"Game"];
    [gameQuery whereKey:@"players" equalTo:[PFUser currentUser]];
    [gameQuery orderByDescending:@"createdAt"];

    [gameQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){

        @synchronized(self.allGames) {
            self.allGames = [NSArray arrayWithObjects: [NSMutableArray array], [NSMutableArray array], [NSMutableArray array], nil];
            // These two instance variables contain values used in asynchronous blocks, to know how much responses are still expected
            self.expectedNbGames = [objects count];
            self.fetchedNbGames = 0;
        }

        for(PFObject *object in objects)
        {
            if([object objectForKey:@"isOver"] == [NSNumber numberWithBool:YES])
            {
                @synchronized(self.allGames) {
                    [((NSMutableArray *)self.allGames[2]) addObject:object];
                    self.fetchedNbGames++;
                }
            }

            else
            {
                PFRelation *relation = [object relationForKey:@"whoseTurn"];
                PFQuery *relQuery = [relation query];
                [relQuery findObjectsInBackgroundWithBlock:^(NSArray *userObjects, NSError *error1){

                    NSMutableArray *arr = [NSMutableArray array];
                    for(PFUser *user in userObjects)
                    {
                        [arr addObject:user.objectId];
                    }

                    @synchronized(self.allGames) {
                        if([arr containsObject:[PFUser currentUser].objectId])
                        {
                            [((NSMutableArray *)self.allGames[0]) addObject:object];
                        }

                        else
                            [((NSMutableArray *)self.allGames[1]) addObject:object];
                        }

                        self.fetchedNbGames++;
                        if (self.fetchedNbGames == self.expectedNbGames)
                        {
                            // We have now received the last expected response, it's time to sort everything again in descending order based on the "createdAt" value
                            NSSortDescriptor *dateDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:NO];
                            NSArray *sortDescriptors = [NSArray arrayWithObject:dateDescriptor];
                            // self.allGames[0] is already sorted anyway because it's been left untouched from the initial PFQuery
                            self.allGames[1] = [self.allGames[1] sortedArrayUsingDescriptors:sortDescriptors];
                            self.allGames[2] = [self.allGames[2] sortedArrayUsingDescriptors:sortDescriptors];
                            // And... reload one last time!
                            dispatch_async(dispatch_get_main_queue(), ^{
                                [self.tableView reloadData];
                            });
                        }
                    }

                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self.tableView reloadData];
                    });
                }];
            }

        }

        [self.tableView reloadData];
    }];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    @synchronized(self.allGames) {
        return [self.allGames count];
    }
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    @synchronized(self.allGames) {
        return [[self.allGames objectAtIndex:section] count];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *MyIdentifier = @"MyIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:MyIdentifier];
    }

    @synchronized(self.allGames) {
        PFObject *object = self.allGames[indexPath.section][indexPath.row];
        cell.textLabel.text = [object objectForKey:@"numPlayers"];
    }

    return cell;
}

@synchronized() 构造用于确保线程安全,因为您将从不同的线程(主线程,还有异步块)访问(读取和写入)sell.allGames 数组来自 Parse 回复)。

请注意,我们从异步块中多次分派 reloadData,因为目前您的代码中无法知道所有块何时完成 运行。我们需要在主线程上分派此调用,因为它会触发 UI 更新,而这些更新总是必须在主线程上完成。

看看这是否可行,也许您可​​以使用属性等对其进行一些改进(以避免每次使用 self.allGames 时都写 @synchronized())。

更新:我改进了我的回答,提供了一种在收到所有回复后重新排序所有内容的方法。此过程涉及创建两个新的实例变量(名为 fetchedNbGamesexpectedNbGames 的整数),在收到来自 Parse 的每个响应后,它们必须更新并相互比较。收到所有响应后,可以对结果数组重新排序(可能因为异步发送和处理请求而造成混乱)并再次刷新 table 视图。

这是未经测试的,可能还有很大的改进空间,但是您可以根据自己的需要对其进行调整,您已经了解了基本的想法。

另请参阅 this other SO thread 以更直观地解释与线程和异步调用相关的问题。