问题
I'm storing 5 PFFiles in an array and using getDataInBackgroundWithBlock
to download those files from Parse.
The problem is the order at which they appear in the table view cells is different every time, presumably because the files are download at different speeds due to the different file sizes.
for (PFFile *imageFile in self.imageFiles) {
[imageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *avatar = [UIImage imageWithData:imageData];
[self.avatars addObject:avatar];
cell.userImageView.image = self.avatars[indexPath.row];
}
}];
}
The self.imageFiles
array is in the correct order.
How do I ensure that the images downloaded are added to the self.avatars
array in the same order as the self.imageFiles
?
回答1:
The question has two parts: (1) explicitly, how to maintain the order of results of asynchronous operations, (2) implied by the use of cell
, how to properly handle asynch requests in support of a tableview.
The answer to the first question is simpler: keep the result of the request associated with the parameter for the request.
// change avatars to hold dictionaries associating PFFiles with images
@property(nonatomic,strong) NSMutableArray *avatars;
// initialize it like this
for (PFFile *imageFile in self.imageFiles) {
[avatars addObject:[@{@"pfFile":imageFile} mutableCopy]];
}
// now lets factor an avatar fetch into its own method
- (void)avatarForIndexPath:(NSIndexPath *)indexPath completion:^(UIImage *, NSError *)completion {
// if we fetched already, just return it via the completion block
UIImage *existingImage = self.avatars[indexPath.row][@"image"];
if (existingImage) return completion(existingImage, nil);
PFFile *pfFile = self.avatars[indexPath.row][@"pfFile"];
[pfFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *avatar = [UIImage imageWithData:imageData];
self.avatars[indexPath.row][@"image"] = avatar;
completion(avatar, nil);
} else {
completion(nil, error);
}
}];
}
Okay for part (1). For part 2, your cellForRowAtIndexPath
code must recognize that cells are reused. By the time the asynch image fetch happens, the cell you're working on might have scrolled away. Fix this by not referring to the cell in the completion block (only the indexPath
).
// somewhere in cellForRowAtIndexPath
// we're ready to setup the cell's image view
UIImage *existingImage = self.avatars[indexPath.row][@"image"];
if (existingImage) {
cell.userImageView.image = existingImage;
} else {
cell.userImageView.image = // you can put a placeholder image here while we do the fetch
[self avatarForIndexPath:indexPath completion:^(UIImage *image, NSError *error) {
// here's the trick that is often missed, don't refer to the cell, instead:
if (!error) {
[tableView reloadRowsAtIndexPaths:@[indexPath]];
}
}];
}
Reloading the row in the completion block will cause cellForRowAtIndexPath
to be called again, except on that subsequent call, we'll have an existing image and the cell will get configured immediately.
回答2:
Whilst danh's answer has answered my question, I did manage to solve it shortly after posting the question. I'm capturing the index of each imageFile and making sure they are added to the self.avatars
array in that order.
for (PFFile *imageFile in self.imageFiles) {
NSInteger index = [self.imageFiles indexOfObject:imageFile];
[imageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *avatar = [UIImage imageWithData:imageData];
self.avatars[index] = avatar;
[self.tableView reloadData];
}
}];
}
Then cell.userImageView.image = self.avatars[indexPath.row];
in cellForRowAtIndexPath:
来源:https://stackoverflow.com/questions/29867837/parse-pffile-download-order-ios