使用AFNetworking加载图片的UICollectionView滚动性能

4

我已经阅读了很多关于UICollectionView滚动不流畅的文章,但似乎没有一个直接适用或者它们仍然没有得到解决。

我正在使用AFNetworking来异步加载每个单元格上的图像(95px平方),当图像再次滚动到视图中时,从缓存中恢复图像(通过给出的响应代码0而不是200进行验证)。

这里是我尝试过的:

  • 注释掉weakCell.photoView.image = image;,这样图像就不会显示在屏幕上,滚动会更加流畅(在HTTP获取期间仍然有一些停顿)
  • cellForRowAtIndexPath方法中删除所有AFNetworking代码,滚动会更加流畅(即使自定义单元格阴影等仍然显示在屏幕上)
  • 当我只在屏幕上绘制单元格视图(带阴影)时,对于100个单元格,滚动非常流畅。一旦我开始在屏幕上绘制图像,我的设备上的滚动就非常差,并且在模拟器上也很明显。 Instagram在其个人资料视图中为数百个单元格提供了非常流畅的滚动效果,因此我正在努力接近他们的性能。

有没有什么方法可以改进下面的任何代码以提高滚动性能?

这是我的单元格代码:

#import "PhotoGalleryCell.h"

@implementation PhotoGalleryCell

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        // Setup the background color, shadow, and border
        self.backgroundColor = [UIColor colorWithWhite:0.25f alpha:1.0f];
        self.layer.borderColor = [UIColor blackColor].CGColor;
        self.layer.borderWidth = 0.5f;
        self.layer.shadowColor = [UIColor blackColor].CGColor;
        self.layer.shadowRadius = 3.0f;
        self.layer.shadowOffset = CGSizeMake(0.0f, 2.0f);
        self.layer.shadowOpacity = 0.5f;

        // Make sure we rasterize for retina
        self.layer.rasterizationScale = [UIScreen mainScreen].scale;
        self.layer.shouldRasterize = YES;

        // Add to the content view
        self.photoView = [[UIImageView alloc] initWithFrame:self.bounds];
        [self.contentView addSubview:self.photoView];
    }

    return self;
}

- (void)prepareForReuse
{
    [super prepareForReuse];

    self.photoView.image = nil;
    self.largeImageURL = nil;
}

这是我的UICollectionView代码:

#pragma mark - Collection View Delegates

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return [zePhotos count];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    PhotoGalleryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kPGPhotoCellIdentifier forIndexPath:indexPath];

    // Get a reference to the image dictionary
    NSDictionary *photoDict = [[zePhotos objectAtIndex:indexPath.row] objectForKey:@"image"];

    // Asynchronously set the thumbnail view
    __weak PhotoGalleryCell *weakCell = cell;
    NSString *thumbnailURL = [[photoDict objectForKey:@"thumbnail"] objectForKey:@"url"];
    NSURLRequest *photoRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:thumbnailURL]];
    [cell.photoView setImageWithURLRequest:photoRequest
                          placeholderImage:nil
                                   success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                       weakCell.photoView.image = image;
                                   }
                                   failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                       NSLog(@"Error retrieving thumbnail... %@", [error localizedDescription]);
                                   }];

    // Cache the large image URL in case they tap on this cell later
    cell.largeImageURL = [[photoDict objectForKey:@"large"] objectForKey:@"url"];

    return cell;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    [self performSegueWithIdentifier:@"showPhotoDetail" sender:self];
}

1
你在解决这个问题上有什么进展吗?我也遇到了同样的情况,如果你有后续的进展,我会非常感激。 - Tres
1
@Tres 抱歉,但我现在不得不接受性能下降。AFNetworking声称已经针对UIScrollView的任何子类(例如UITableView或UICollectionView)进行了优化,但这似乎是UI性能不佳的原因。 - ZeNewb
3个回答

1
你可以尝试在cell init中添加shadowPath,这应该会提高性能。这是我在一个项目中使用的代码,用于添加圆角shadowPath(有关更多选择,请参见UIBezierPath方法)。
self.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.frame.bounds
                                              byRoundingCorners:UIRectCornerAllCorners
                                                    cornerRadii:CGSizeMake(10, 10)].CGPath;

此外,如果我没记错的话,AFNetworking 并不会调整从服务器返回的图像大小,因此这可能会对您的图像质量产生影响(尽管您已经在 UIImageView 中添加了缩放方法)。如果您希望调整图像大小,我建议将返回的图像分派到相应的处理程序进行调整。
CGSize targetSize = cell.photoView.bounds.size;
[cell.photoView setImageWithURLRequest:photoRequest
                      placeholderImage:nil
                               success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                       CGFloat imageHeight = image.size.height;
                                       CGFloat imageWidth = image.size.width;

                                       CGSize newSize = weakCell.imageView.bounds.size;
                                       CGFloat scaleFactor = targetSize.width / imageWidth;
                                       newSize.height = imageHeight * scaleFactor;

                                       UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
                                       [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
                                       UIImage *small = UIGraphicsGetImageFromCurrentImageContext();
                                       UIGraphicsEndImageContext();

                                       dispatch_async(dispatch_get_main_queue(),^{
                                           weakCell.photoView.image = small;
                                       });
                                   });
                               }
                               failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                   NSLog(@"Error retrieving thumbnail... %@", [error localizedDescription]);
                               }];

感谢您的回答。正如我在原帖中所说:当我仅在屏幕上绘制单元格视图(带有阴影)时,对于100个单元格,滚动非常流畅。但是,一旦我开始在屏幕上绘制图像,我的设备上的滚动就非常差,并且即使在模拟器上也很明显。因此,这似乎是AFNetworking图像缓存的问题。 - ZeNewb

0
问题在于当您快速滚动时,会同时启动数百个网络请求。如果您已经缓存了图像,请立即显示它。如果没有,请在表视图减速时才开始下载。
您可以使用类似以下的代码:
//Properties or Instance Variables
NSDate *scrollDateBuffer;
CGPoint scrollOffsetBuffer;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    NSTimeInterval secondsSinceLastScroll = [[NSDate date] timeIntervalSinceDate:scrollDateBuffer];
    CGFloat distanceSinceLastScroll = fabsf(scrollView.contentOffset.y - scrollOffsetBuffer.y);

    BOOL slow = (secondsSinceLastScroll > 0 && secondsSinceLastScroll < 0.02);
    BOOL small = (distanceSinceLastScroll > 0 && distanceSinceLastScroll < 1);

    if (slow && small) {
        [self loadImagesForOnscreenRows];
    }

    scrollDateBuffer = [NSDate date];
    scrollOffsetBuffer = scrollView.contentOffset;
}

您需要在其他方法中调用loadImagesForOnscreenRows,例如当新数据到来时、viewWillAppearscrollViewDidScrollToTop

以下是loadImagesForOnscreenRows的示例实现:

- (void)loadImagesForOnscreenRows
{
    @try {
        for (UITableViewCell *cell in self.tableView.visibleCells) {
            // load your images
            NSURLRequest *photoRequest = …;
            if (photoRequest) {
                [cell.photoView setImageWithURLRequest:…];
            }
        }
    }
    @catch (NSException *exception) {
        NSLog(@"Exception when loading table cells: %@", exception);
    }
}

我在try/catch块中使用这个代码,因为根据我的经验,[UITableView -visibleCells]不是很可靠——它偶尔会返回已释放的单元格或没有superview的单元格。如果你确保只有在表格不快速滚动时才调用此方法,它不应该对滚动性能产生太大影响。

另外,请注意AFNetworking UIImageView类别不公开缓存对象。你需要稍微修改一下来检查是否已经缓存了图像;this answer应该指引你正确的方向。


请注意,我的答案包括有关UITableView的信息,而您正在使用UICollectionView。相同的答案适用,因为UICollectionView遵循相同的设计模式。 - Aaron Brager
抱歉回复晚了!AFNetworking应该已经针对任何UIScrollView的子类进行了优化,例如您列出的那些,因此这个问题应该已经得到解决。 - ZeNewb

0

代码检查看起来不错,尽管我敢打赌是阴影合成导致了很大的延迟。找出造成延迟的确切原因的方法是使用Instruments中的时间分析器工具。这里是来自苹果的文档


1
当我仅在屏幕上绘制单元格视图(带有阴影),对于100个单元格,滚动非常流畅。一旦我开始在屏幕上绘制图像,我的设备上的滚动就会非常差,并且模拟器上也会明显感觉到。Instagram在其个人资料视图中可以流畅地滚动数百个单元格,因此我正在努力接近他们的性能。 - ZeNewb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接