嵌套查询块将数据加载到 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()
)。
更新:我改进了我的回答,提供了一种在收到所有回复后重新排序所有内容的方法。此过程涉及创建两个新的实例变量(名为 fetchedNbGames
和 expectedNbGames
的整数),在收到来自 Parse 的每个响应后,它们必须更新并相互比较。收到所有响应后,可以对结果数组重新排序(可能因为异步发送和处理请求而造成混乱)并再次刷新 table 视图。
这是未经测试的,可能还有很大的改进空间,但是您可以根据自己的需要对其进行调整,您已经了解了基本的想法。
另请参阅 this other SO thread 以更直观地解释与线程和异步调用相关的问题。
我有一个带有 tableView 作为子视图的 UIViewController。 table 视图将包含 3 个部分,我使用问题
-(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()
)。
更新:我改进了我的回答,提供了一种在收到所有回复后重新排序所有内容的方法。此过程涉及创建两个新的实例变量(名为 fetchedNbGames
和 expectedNbGames
的整数),在收到来自 Parse 的每个响应后,它们必须更新并相互比较。收到所有响应后,可以对结果数组重新排序(可能因为异步发送和处理请求而造成混乱)并再次刷新 table 视图。
这是未经测试的,可能还有很大的改进空间,但是您可以根据自己的需要对其进行调整,您已经了解了基本的想法。
另请参阅 this other SO thread 以更直观地解释与线程和异步调用相关的问题。