UICollectionView 中的内存警告(带图像)

Posted

技术标签:

【中文标题】UICollectionView 中的内存警告(带图像)【英文标题】:Memory Warning in UICollectionView (with image) 【发布时间】:2013-06-08 07:57:16 【问题描述】:

我目前正在处理包含大量图像的 UICollectionView。但是,它有时会在此视图中崩溃并显示内存警告。我正在使用 AFNetworking 和 UIImageView+AFNetworking 类别通过 setImageWithURL: 方法设置图像。一个问题可能是缓存。我不确定 AFNetworking 是否处理图像缓存。无论如何,有没有办法在内存管理方面优化这段代码?或者如果我要在这个视图控制器中实现 didReceiveMemoryWarning 方法,这个方法可以放什么?我附上了这个集合视图的 cellForItemAtIndexPath 代码。

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"RecipeCell" forIndexPath:indexPath];

// setting the image view for the cell using AFNetworking. Does this do caching automatically?
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:6];
if (PRODUCTION) 
    [recipeImageView setImageWithURL:[[self.recipes objectAtIndex:indexPath.row] objectForKey:@"recipe_image"] placeholderImage:[UIImage imageNamed:@"default_recipe_picture.png"]];
 else 
    [recipeImageView setImageWithURL:[NSString stringWithFormat:@"http://localhost:5000/%@", [[self.recipes objectAtIndex:indexPath.row] objectForKey:@"recipe_image"]] placeholderImage:[UIImage imageNamed:@"default_recipe_picture.png"]];



// configure the back of the cell. fill all the info.
UITextView *recipeNameView = (UITextView *)[cell viewWithTag:8];
recipeNameView.text = [NSString stringWithFormat:@"%@", [[self.recipes objectAtIndex:indexPath.row] objectForKey:@"recipe_name"]];

UILabel *recipeNameLabel = (UILabel *)[cell viewWithTag:2];
recipeNameLabel.text = [NSString stringWithFormat:@"%@", [[self.recipes objectAtIndex:indexPath.row] objectForKey:@"recipe_name"]];

NSDictionary *user = [[self.recipes objectAtIndex:indexPath.row] objectForKey:@"user"];
UIButton *chefNameButton = (UIButton *)[cell viewWithTag:3];
[chefNameButton setTitle:[NSString stringWithFormat:@"%@ %@", [user objectForKey:@"first_name"], [user objectForKey:@"last_name"]] forState:UIControlStateNormal];

NSMutableArray *missingIngredientsStringArray = [[NSMutableArray alloc] init];
NSArray *missingIngredients = [[self.recipes objectAtIndex:indexPath.row] objectForKey:@"missing_ingredients"];
for (NSDictionary *missingIngredient in missingIngredients) 
    [missingIngredientsStringArray addObject:[missingIngredient objectForKey:@"name"]];

NSString *missingIngredientsString = [missingIngredientsStringArray componentsJoinedByString:@","];

UITextView *missingIngredientsView = (UITextView *)[cell viewWithTag:4];
missingIngredientsView.text = [NSString stringWithFormat:@"%u Missing Ingredients: %@", missingIngredients.count, missingIngredientsString];

// configure the front of the cell. chef name button and missing ingredients and likes on front view
UIButton *frontNameButton = (UIButton *)[cell viewWithTag:11];
[frontNameButton setTitle:[NSString stringWithFormat:@"%@ %@", [user objectForKey:@"first_name"], [user objectForKey:@"last_name"]] forState:UIControlStateNormal];
[frontNameButton sizeToFit];
frontNameButton.frame = CGRectMake(160 - [frontNameButton.titleLabel.text sizeWithFont:[UIFont boldSystemFontOfSize:13]].width - 7, frontNameButton.frame.origin.y, frontNameButton.frame.size.width, frontNameButton.frame.size.height);

UILabel *likesLabel = (UILabel *)[cell viewWithTag:9];
likesLabel.text = [NSString stringWithFormat:@"%@ likes", [[self.recipes objectAtIndex:indexPath.row] objectForKey:@"likes"]];

UIButton *missingIngredientsButton = (UIButton *)[cell viewWithTag:12];
[missingIngredientsButton setBackgroundImage:[UIImage imageNamed:@"badge_green.png"] forState:UIControlStateSelected];
if (missingIngredients.count == 0) 
    missingIngredientsButton.selected = YES;
    [missingIngredientsButton setTitle:@"" forState:UIControlStateNormal];
 else 
    missingIngredientsButton.selected = NO;
    [missingIngredientsButton setTitle:[NSString stringWithFormat:@"%u", missingIngredients.count] forState:UIControlStateNormal];


// make back view invisible.
UIView *backView = [cell viewWithTag:1];
UIView *frontView = [cell viewWithTag:5];
frontView.alpha = 1.0;
backView.alpha = 0;

// adding flip gesture recognizers
UIView *flipView1 = [cell viewWithTag:12];
UIView *flipView2 = [cell viewWithTag:1];

UITapGestureRecognizer *flipGestureRecognizer1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(flipCell:)];
UITapGestureRecognizer *flipGestureRecognizer2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(flipCell:)];
[flipView1 addGestureRecognizer:flipGestureRecognizer1];
[flipView2 addGestureRecognizer:flipGestureRecognizer2];

return cell;

[编辑] 我附上了我的 Instruments 运行的屏幕截图。

你可以看到内存分配增加了,因为我只是反复按下 segue 并按下返回按钮。不断增加的东西是 CFData、CALayer、CABackingStore、UITableView。我怀疑这些是segue之后创建的东西,并且它们没有被释放......请帮助!

【问题讨论】:

【参考方案1】:

您可能需要某种图像缓存策略来避免重新下载图像。 UIImageView+AFNetworking 类别会为您缓存图像。但是您可能还会将响应缓存在内存中的 URL 缓存中,这在这种情况下有些多余。

因此您可以考虑减少或关闭内存中的 URL 缓存。我遇到了您描述的问题,以下内容大大减少了我的记忆问题:

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];

【讨论】:

感谢您的建议。我有一个问题。共享 URL 缓存是否只处理图像? AFNetworking 默认不启用 NSURLCache;仅当您创建一个时才会启用它。此外,两个缓存都指向内存中的相同图像数据,所以我怀疑这会节省太多内存。 @harakang - 请参阅 nshipster.com/nsurlcache - 它根据缓存策略缓存了所有内容。 “AFNetworking 默认不启用 NSURLCache” 据我了解(尽管我不是专家),共享 URL 缓存是开箱即用的。 AFNetworking 不需要启用它。 @AaronBrager “另外,两个缓存都指向内存中的相同图像数据” 好点。我们实际上离开了UIImageView+AFNetworking 以获得更细粒度的控制(不缓存全尺寸图像+缓存解压缩的缩略图图像)。所以在我们的案例中,这很重要。 @AaronBrager @TimothyMoose - 我实际上错了,他们指向内存中的相同数据。 NSURLCache缓存HTTP响应的数据; AFImageCache 缓存 UIImage 对象。所以如果你有一个NSURLCache,你会缓存两次。【参考方案2】:

AFNetworking 自动将图像存储在NSCache 集合中,当内存不足警告时,它会自动从内存中删除部分或全部图像。 AFNetworking 可能不是您的问题。

事实上,我不认为显示图像是您的问题,除非您下载大量非常大的图像并同时显示它们。 (如果是这种情况,您应该尝试优化图像以在设备上显示,这样就不需要调整它们的大小。)

我看到的一个问题是,您每次进入视图时都会向单元格添加手势识别器,但单元格会被重复使用,因此当单元格再次进入时,您会向其添加不必要的手势识别器。您可以通过继承 UITableViewCell 并将手势识别器分配为属性来解决此问题。您还可以通过检查flipView1flipView2 来解决此问题,以查看它们是否在添加手势识别器之前附加了它们。 (我不确定这是否足以引起内存警告。)

我建议转到 Build -> Profile 并选择 Allocations 工具。在左侧,仅选择 Objective C,并隐藏系统调用。然后,滚动浏览您的收藏视图并查看仪器以查看占用所有内存的内容。

更新

这是分配工具的屏幕截图:

【讨论】:

感谢您的建议。尽管手势识别器对内存不是那么重要,但这绝对是需要注意的事情。我正在对分配和泄漏进行分析,但我认为我无法从数据中提取有用的信息。我只是看到实时和总字节数增加,有时当我打开搜索显示控制器时 malloc 64 字节会大幅增加。无论如何,我将使用 Objective C only 选项再试一次! 我还有一个问题。在 Allocations 工具中,我应该在哪里选择 Objective C only 选项?在左侧,我只看到了 Heapshot Analysis、Allocation Lifespan、Call tr​​ee、Call tr​​ee constraints 和 Specific Data Mining。 @HaraKang - 用截图更新了我的答案。 感谢您的建议。但是一件奇怪的事情是,当我单击 Show Obj-C only 时,我在列表中什么都没有。但是,当我使用 Instruments 时,我感觉执行 popViewControllerAnimated 方法的后退按钮没有正确释放内存。我还附上了我的仪器结果。你能看看他们吗?例如,即使我按下后退按钮,UITableView 的内存也会不断增加。这是否意味着我在某处有一个保留周期? 我将 Profiling 的方案更改为调试,现在它只显示 obj-c 列表!我发现大部分没有发布的东西都是从 initWithCoder 方法调用的,这是故事板的问题,对吧?

以上是关于UICollectionView 中的内存警告(带图像)的主要内容,如果未能解决你的问题,请参考以下文章

使用 UICollectionView 收到内存警告以查看图像

UICollectionView 在滚动时收到内存警告

在 UICollectionView iOS 7 中收到内存警告

收到内存警告。带 ipad 相机

UICollectionView 中的高内存使用 [重复]

在 UICollectionView 中加载用户相册时内存增长失控