2015-02-12 53 views
0

我有一个UIViewController与一个tableView作为子视图。该tableview将有3个部分,我用我的问题PFQueryTableViewController with 3 Sections的答案来完成这一点,但数据没有得到加载到表中。我在viewWillAppear的后台执行我的查询。我担心这是由于嵌套的查询块。我怎样才能解决这个问题?这里是我的代码:嵌套查询块加载数据到UITableView

-(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; 
} 
+0

您是否设置了一些断点来检查是否有效调用了所有内容,包括子查询中的代码? – Romain 2015-02-12 06:04:12

+0

@Romain当我在这里粘贴代码时,我有一堆日志语句被删除。一切都被调用,看起来好像在我创建“allGames”数组时这些数组是空的。 – Jacob 2015-02-12 06:06:14

+1

您的Parse查询异步运行,因此,在您调用'reloadData'时,'allGames'使用3个空数组进行初始化。由于你的数据结构是已知的(一个由3个数组组成的数组),为什么不用3个空数组*在运行你的Parse查询之前初始化它,然后在回调块中直接更新它(通过适当的线程同步)并最后调用' reloadData'?这最终会更频繁地调用'reloadData',但是iOS应该可以使用它,如果不是,那么稍后您仍然可以使用工作代码在后面找到优化。 – Romain 2015-02-12 06:23:18

回答

2

这里是你可以尝试做的:

-(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())。

UPDATE:我已经通过提供一种方法在接收到所有响应后重新排序所有内容,从而改进了我的答案。这个过程涉及创建两个新的实例变量(名为fetchedNbGamesexpectedNbGames的整数),必须在从Parse收到每个响应后对其进行更新和比较。一旦收到所有响应,结果数组就可以重新排序(可能由于请求被异步发送和处理而导致无序),并且表视图再次刷新。

这是未经测试的,可能还有很大的改进空间,但是取决于您的需求,您可以根据自己的需求进行调整,从而获得基本的想法。

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

+0

如果我在解析中将我的列更改为PFObjects的数组而不是关系,那么我不必运行第二个后台查询,对吧?我只能检索数组并比较对象。 – Jacob 2015-02-12 17:23:24

+0

我想是的,是的。但是你目前的设计也可能有其原因。你试过我提供的解决方案吗? – Romain 2015-02-12 17:28:07

+0

是的,我正在实施它,但我的互联网连接很差,所以查询没有执行。 – Jacob 2015-02-12 17:28:52