Eri*_*ric 5 iphone grand-central-dispatch ipad ios uicollectionview
我需要调整大型本地存储的图像(包含在其中self.optionArray),然后在collectionView中显示它.如果我只是显示它,iOS会尝试调整图像大小,因为我快速滚动导致与内存相关的崩溃.
在下面的代码中,collectionView将平滑滚动,但有时如果我滚动得非常快,则会显示一个不正确的图像,然后在滚动减速时更改为正确的图像.为什么不设置cell.cellImage.image来nil解决这个?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath];
cell.cellImage.image = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
cell.cellImage.image = nil;
UIImage *test = [self.optionArray objectAtIndex:indexPath.row];
UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
dispatch_sync(dispatch_get_main_queue(), ^{
cell.cellImage.image = localImage2
cell.cellTextLabel.text = @"";
[cell setNeedsLayout];
});
});
}
return cell;
}
- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Run Code Online (Sandbox Code Playgroud)
编辑:我添加了另一个async来缓存第一个和nil并初始化cell.image.我在初始快速向下滚动时遇到了同样的问题.然而,在卷轴上,它现在完美无瑕.
我补充说:
-(void)createDictionary
{
for (UIImage *test in self.optionArray) {
UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
[localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:[self.optionArray indexOfObject:test]]];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (!localImageDict) {
localImageDict = [[NSMutableDictionary alloc]initWithCapacity:self.optionArray.count];
}
else {
[localImageDict removeAllObjects];
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[self createDictionary];
});
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath];
cell.cellImage.image = nil;
cell.cellImage.image = [[UIImage alloc]init];
if ([localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]]) {
cell.cellImage.image = [localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]];
cell.cellTextLabel.text = @"";
}
else {
cell.cellImage.image = nil;
cell.cellImage.image = [[UIImage alloc]init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *test = [self.optionArray objectAtIndex:indexPath.row];
UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
[localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:indexPath.row]];
dispatch_sync(dispatch_get_main_queue(), ^{
cell.cellImage.image = shownImage;
cell.cellTextLabel.text = @"";
[cell setNeedsLayout];
});
});
}
}
return cell;
Run Code Online (Sandbox Code Playgroud)
仔细看看你的代码示例,我可以看到你的内存问题的根源.跳出来的最重要的问题是您似乎将所有图像保存在一个数组中.这需要非常大的内存(我推断你需要调整它们必须大的图像).
要减少应用程序的占用空间,您不应该维护一个UIImage对象数组.相反,只需维护一个URL或路径到您的图像,然后只UIImage在UI需要时动态创建对象(一个称为延迟加载的过程).一旦图像离开屏幕,你就可以释放它(UICollectionView就像UITableView你没有对图像保持强烈的引用一样,就像你做了很多清理工作一样).
应用程序通常只应维护UIImage当前可见图像的对象.您可能NSCache出于性能原因缓存这些已调整大小的图像(例如,使用),但是当内存不足时,将自动清除缓存.
好的是你显然已经精通异步处理.无论如何,实现可能如下所示:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath];
NSString *filename = [self.filenameArray objectAtIndex:indexPath.row]; // I always use indexPath.item, but if row works, that's great
UIImage *image = [self.thumbnailCache objectForKey:filename]; // you can key this on whatever you want, but the filename works
cell.cellImage.image = image; // this will load cached image if found, or `nil` it if not found
if (image == nil) // we only need to retrieve image if not found in our cache
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *test = [UIImage imageWithContentsOfFile:filename]; // load the image here, now that we know we need it
if (!test)
{
NSLog(@"%s: unable to load image", __FUNCTION__);
return;
}
UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
if (!localImage2)
{
NSLog(@"%s: unable to convert image", __FUNCTION__);
return;
}
[self.thumbnailCache setObject:localImage2 forKey:filename]; // save the image to the cache
dispatch_async(dispatch_get_main_queue(), ^{ // async is fine; no need to keep this background operation alive, waiting for the main queue to respond
// see if the cell for this indexPath is still onscreen; probably is, but just in case
CustomTabBarCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell)
{
updateCell.cellImage.image = localImage2
updateCell.cellTextLabel.text = @"";
[updateCell setNeedsLayout];
}
});
});
}
return cell;
}
Run Code Online (Sandbox Code Playgroud)
这假定您定义了一个类属性,thumbnailCache该属性是NSCache对您将初始化viewDidLoad或在任何地方初始化的类的强引用.缓存是一种充分利用两个世界的方法,在内存中加载图像以获得最佳性能,但是当您遇到内存压力时它将被释放.
显然,我很乐意假设"哦,只需用一系列图像文件名替换你的图像阵列",我知道你可能需要进入代码的一些不同部分才能使它工作,但是这个毫无疑问是你的记忆消耗的来源.很明显,你总是会遇到其他内存问题(保留周期等),但在你发布的代码片段中没有类似内容.