iOS UIImageView 内存没有在 ARC 上被释放

Posted

技术标签:

【中文标题】iOS UIImageView 内存没有在 ARC 上被释放【英文标题】:iOS UIImageView memory not getting deallocated on ARC 【发布时间】:2014-12-17 11:07:27 【问题描述】:

我想以圆形路径为图像视图设置动画,并在单击该图像视图时需要更改新图像。我的问题是我分配给图像视图的图像没有被释放。并且应用程序收到内存警告并崩溃。我浏览并尝试了很多解决这个问题的方法,但没有用。就我而言,我需要从 Objective c 类创建所有 ui 组件。这里贴出创建图像视图和动画的代码。

@autoreleasepool 
    for(int i= 0 ; i < categories.count; i++)
    
        NSString *categoryImage = [NSString stringWithFormat:@"%@_%ld.png",[categories objectAtIndex:i],(long)rating];
        if (paginationClicked) 
            if([selectedCategories containsObject:[categories objectAtIndex:i]])
                categoryImage = [NSString stringWithFormat:@"sel_%@",categoryImage];
            
        
        UIImageView *imageView = [[UIImageView alloc] init];

        imageView.image = [self.mySprites objectForKey:categoryImage];
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        imageView.clipsToBounds = NO;
        [imageView sizeToFit];
        imageView.accessibilityHint = [categories objectAtIndex:i];
//        imageView.frame = CGRectMake(location.x+sin(M_PI/2.5)*(self.view.frame.size.width*1.5),location.y+cos(M_PI/2.5)*(self.view.frame.size.width*1.5) , 150, 150);
        imageView.userInteractionEnabled = YES;
        imageView.multipleTouchEnabled = YES;
        UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                    action:@selector(categoryTapGestureCaptured:)];
        singleTap.numberOfTapsRequired = 1;
        [imageView addGestureRecognizer:singleTap];
        [categoryView addSubview:imageView];

        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

        UIBezierPath *path = [UIBezierPath bezierPath];
        [path addArcWithCenter:location
                        radius:self.view.frame.size.width*1.5
                    startAngle:0.8
                      endAngle:-0.3+(0.1*(i+1))
                     clockwise:NO];

        animation.path = path.CGPath;
        imageView.center = path.currentPoint;
        animation.fillMode              = kCAFillModeForwards;
        animation.removedOnCompletion   = NO;
        animation.duration              = 1+0.25*i;
        animation.timingFunction        = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

        // Apply it
        [imageView.layer addAnimation:animation forKey:@"animation.trash"];
   
    

这是在点击时更改图像的代码。

for (UIImageView *subview in subviews) 
        NSString *key = [NSString stringWithFormat:@"%@_%ld.png",subview.accessibilityHint,(long)rating];
        if ([SelectedCategory isEqualToString:subview.accessibilityHint]) 
            NSString *tempSubCategory = [categoryObj objectForKey:SelectedCategory];
            if([selectedCategories containsObject:SelectedCategory])
                subview.image = [self.mySprites objectForKey:key];
                [selectedCategories removeObject:SelectedCategory];
                if (tempSubCategory.length != 0) 
                    subCategoriesAvailable = subCategoriesAvailable-1;
                
                [self showNoPagination:subCategoriesAvailable+2];
            else
                if(selectedCategories.count != 2)
                key = [NSString stringWithFormat:@"sel_%@",key];
                subview.image = [self.mySprites objectForKey:key];
                [selectedCategories addObject:SelectedCategory];
                    if ([SelectedCategory isEqualToString:@"Other"]) 
                        [self showCommentDialog];
                    else
                        if (tempSubCategory.length != 0) 
                            subCategoriesAvailable = subCategoriesAvailable+1;
                        
                        [self showNoPagination:subCategoriesAvailable+2];
                    
                
            
            [self disableCategories];
            break;
        
    

而且我不知道这里做错了什么。我尝试取消 for 循环,但没有用。

我用于删除图像视图的代码

UIView *categoryView = [self.view viewWithTag:500];
    NSArray *subviews = [categoryView subviews];
    for (UIImageView *subview in subviews) 
        if(![selectedCategories containsObject:subview.accessibilityHint])
            [subview removeFromSuperview];
            subview.image = Nil;
        
    

添加精灵阅读器代码以供参考

#import "UIImage+Sprite.h"
#import "XMLReader.h"

@implementation UIImage (Sprite)

+ (NSDictionary*)spritesWithContentsOfFile:(NSString*)filename

    CGFloat scale = [UIScreen mainScreen].scale;
    NSString* file = [filename stringByDeletingPathExtension];
    if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && 
        (scale == 2.0))
    
        file = [NSString stringWithFormat:@"%@@2x", file];
    
    NSString* extension = [filename pathExtension];
    NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@.%@", file,extension]];
    NSError* error = nil;
    NSDictionary* xmlDictionary = [XMLReader dictionaryForXMLData:data error:&error];
    NSDictionary* xmlTextureAtlas = [xmlDictionary objectForKey:@"TextureAtlas"];
    UIImage* image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@.%@", file,[[xmlTextureAtlas objectForKey:@"imagePath"]pathExtension]]];
    CGSize size = CGSizeMake([[xmlTextureAtlas objectForKey:@"width"] integerValue],
                             [[xmlTextureAtlas objectForKey:@"height"] integerValue]);

    if (!image || CGSizeEqualToSize(size, CGSizeZero)) return nil;
    CGImageRef spriteSheet = [image CGImage];
    NSMutableDictionary* tempDictionary = [[NSMutableDictionary alloc] init];

    NSArray* xmlSprites = [xmlTextureAtlas objectForKey:@"sprite"];
    for (NSDictionary* xmlSprite in xmlSprites)
    
        CGRect unscaledRect = CGRectMake([[xmlSprite objectForKey:@"x"] integerValue],
                                         [[xmlSprite objectForKey:@"y"] integerValue],
                                         [[xmlSprite objectForKey:@"w"] integerValue],
                                         [[xmlSprite objectForKey:@"h"] integerValue]);
        CGImageRef sprite = CGImageCreateWithImageInRect(spriteSheet, unscaledRect);
        // If this is a @2x image it is twice as big as it should be.
        // Take care to consider the scale factor here.
        [tempDictionary setObject:[UIImage imageWithCGImage:sprite scale:scale orientation:UIImageOrientationUp] forKey:[xmlSprite objectForKey:@"n"]];
        CGImageRelease(sprite);
    

    return [NSDictionary dictionaryWithDictionary:tempDictionary];


@end

请帮我解决这个问题。提前致谢。

【问题讨论】:

您正在将图像视图添加到类别视图,但从子视图中删除??这是什么子视图。你在哪里删除显示代码中的图像?? 我正在使用 NSArray *subviews = [[self.view viewWithTag:500] subviews];获取类别视图的所有子视图并更改检测到点击手势的图像。 好的,那么你在哪里删除图像,我可以看到你正在从数组中删除对象。像 imageView.image = nil;[imageview removeFromSupreView] 这样删除图像和 imageView 的代码在哪里。我猜在点击时您每次都在分配 imageView。如果是这样,在再次分配之前,将图像设为 nil,并从 supreView 中删除 imageView。我猜对了吗?? 嗨,我更新了这个问题。请检查我用来删除图像视图的代码。我已经按照你说的做了,没有运气:( 【参考方案1】:

看起来所有图像都被字典(假设)self.mySprites 保留,因为您正在通过调用 imageView.image = [self.mySprites objectForKey:categoryImage]; 加载它们

如果您使用+[UIImage imageNamed:] 将图像加载到字典中,则字典最初仅包含压缩的 png 图像。图像在渲染到屏幕时会从 png 解压缩为位图,并且这些解压缩的图像使用大量 RAM(即您看到的标记为“ImageIO_PNG_Data”的内存使用情况)。如果字典保留了它们,那么每次你在屏幕上渲染一个新的时内存都会增长,因为解压缩的数据保存在字典保留的UIImage 对象中。

可供您选择的选项:

将图像名称存储在self.mySprites 字典中,并按需加载图像。您应该知道+[UIImage imageNamed:] 实现了内部 RAM 缓存以加快速度,因此如果图像很大,这也可能会导致内存问题,因为缓存不会很快清除。如果这是一个问题,请考虑使用+[UIImage imageWithContentsOfFile:],尽管它需要一些额外的代码(不多),不会在 RAM 中缓存图像。

self.mySprites 重新实现为NSCacheNSCache 会在内存压力过高时开始扔东西,所以你需要处理你期望的图像不存在的情况,并从磁盘加载它(可能使用上述技术)

【讨论】:

感谢@P-double 的即时回复。实际上正在使用精灵表作为图标。 Mysprites 是 sprite reader 类的对象,它使用 imageWithContentsOfFile: 和 CGimageref 并且也发布了。如果能解决我的问题,你能告诉我如何使用 nscache。谢谢 能看到MySprites类的代码吗? [tempDictionary setObject:[UIImage imageWithCGImage:sprite scale:scale orientation:UIImageOrientationUp] forKey:[xmlSprite objectForKey:@"n"]]; 这会将图像添加到字典中,字典会保留它,图像是否曾经从字典中删除? 我不知道它是否保留,但这是将图像添加到字典的代码。如果是这样,我该如何纠正。 您遇到的根本问题是设备没有足够的内存来将您的所有资产保存在解压缩状态。您的架构是一次将所有资产加载到内存中,这永远行不通。您需要将您拥有的方法替换为加载并返回单个 UIImage 的方法,而不是所有这些 UIImage 的字典。流程类似于加载请求的资产->在图像视图上设置->用户点击->加载新资产->在图像视图上替换资产->旧图像由图像视图释放并释放【参考方案2】:

CAKeyframeAnimation 继承自 CAPropertyAnimation,后者实际上继承自 CAAnimation。

如果你看到 CAAnimation 类的委托,它被强烈引用,如下所示 -

/* 动画的委托。此对象保留为 * 动画对象的生命周期。默认为零。见下文 * 支持的委托方法。 */

@property(strong) id 委托人;

现在你已经在 imageView.layer 上添加了动画的引用,这样做的话 imageView.layer 的引用将被 CAAnimation 引用强保留。

你也设置了 动画.removedOnCompletion = NO;

它不会在完成时从图层中删除动画

所以如果你完成了一个图像视图,那么首先从它的层中删除所有动画,然后释放图像视图。

我认为由于 CAAnimation 强烈引用 imageView 引用(它也会增加它的保留计数),这可能是您从超级视图中删除了 imageView 的原因,之后它的保留计数仍然不会为零,并且所以导致泄漏。

【讨论】:

我试过给animation.removedOnCompletion = YES;当动画完成时,我给出了 removeallanimations 但没有效果。我还需要做什么吗?【参考方案3】:

有什么具体要求要设置

animation.removedOnCompletion = NO;

因为,设置

animation.removedOnCompletion = YES;

可以解决与内存泄漏有关的问题。

【讨论】:

【参考方案4】:

或者,要解决内存泄漏问题,您可以通过实现 CAAnimation 的委托来删除相应 imageView' 层上的相应动画,如下所示 -

/* 当动画完成其活动持续时间或完成时调用 * 从它所附加的对象(即图层)中删除。 '旗帜' * 如果动画达到其活动持续时间的结尾,则为真 * 没有被删除。 */

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 
    if (flag) 
        NSLog(@"%@", @"The animation is finished. Do something here.");
    

     //Get the reference of the corresponding imageView
    [imageView.layer removeAnimationForKey:@"animation.trash"];


 UIView *categoryView = [self.view viewWithTag:500];
    NSArray *subviews = [categoryView subviews];
    for (UIImageView *subview in subviews) 
        if(![selectedCategories containsObject:subview.accessibilityHint])
            [subview.layer removeAnimationForKey:@"animation.trash"]; // either this
            //or// [subview.layer removeAllAnimations]; //alternatively
            [subview removeFromSuperview];
            subview.image = Nil;
        
    

【讨论】:

动画完成后是否调用了animationDidStop方法?你设置了animation.delegate = self; ? animationDidStop 方法被调用。但是内存问题没有解决。

以上是关于iOS UIImageView 内存没有在 ARC 上被释放的主要内容,如果未能解决你的问题,请参考以下文章

iOS:ARC,不释放内存

将 uiimage 设置为 nil 不会使用 ARC 释放内存

IOS开发 arc与非Arc代码的区别

iOS内存管理-ARC

iOS 5编程 内存管理 ARC技术概述

在 iOS 中使用 UIWebView 来渲染图像而不是 UIImageView 使用的内存要少得多。说得通?