iOS UIImageView内存未在ARC上取消分配

时间:2014-12-17 11:07:28

标签: ios memory-management uiimageview automatic-ref-counting

我想在圆形路径中动画图像视图,然后单击图像,图像视图需要更改新图像。我的问题是我分配给图像视图的图像没有被释放。应用程序收到内存警告并崩溃。我冲浪并为这个问题尝试了很多解决方案,但没有用。在我的情况下,我需要从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循环上取消,但没有用。

Allocation inspector

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

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

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

4 个答案:

答案 0 :(得分:2)

看起来所有图像都被字典保留(假设)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重新实施为NSCache。当内存压力过高时,NSCache会开始抛出东西,所以你需要处理图像不存在的情况,并从磁盘上加载(也许使用上面的内容)技术)

答案 1 :(得分:0)

CAKeyframeAnimation继承自CAPropertyAnimation,后者继承自CAAnimation。

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

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

@property(强)id委托;

现在你已经在imageView.layer上添加了动画参考,这样就可以通过CAAnimation参考强烈保留imageView.layer的引用。

你也设定了 animation.removedOnCompletion = NO;

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

因此,如果您已完成图像视图,则首先从其图层中删除AllAnimations,然后释放图像视图。

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

答案 2 :(得分:0)

是否有任何具体要求设置

animation.removedOnCompletion = NO;

因为,设置

animation.removedOnCompletion = YES;

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

答案 3 :(得分:0)

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

/ *当动画完成其活动持续时间或时,调用  *从它所附着的物体(即该层)中移除。 &#39;标志&#39;  *如果动画达到其活动持续时间的结束,则为true  *没有被删除。 * /

- (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;
        }
    }