【问题标题】:UIImage dealloc is never calledUIImage dealloc 永远不会被调用
【发布时间】:2012-05-09 09:13:09
【问题描述】:

我试图弄清楚为什么我的应用程序使用大量内存,然后在多次内存警告后崩溃。 Instrument VM Tracker 显示它使用 ~30Mb 作为脏内存。分配显示 10-15 Mb 不是那么多。 一旦应用程序处理显示大量图像(缩略图),我认为图像是我应该看的。 我不使用标准的 UIImage imageNamed 方法,而是使用 imageWithData 并进行缓存。当系统发送内存警告时,我会清理缓存存储。为了确保我创建的图像在不再需要时被销毁,我将 UIImage 子类化并覆盖了 imageWithData、release 和 dealloc 方法。我可以看到 imageWithData 被调用,但 release 和 dealloc 从未被调用。 我就是这样做的:

BaseUIimage.h

@interface BaseUIimage : UIImage

@end

BaseUIimage.m

#import "BaseUIimage.h"

@implementation BaseUIimage

+ (id)imageWithData:(NSData *)data {
    NSLog(@"UIImage imageWithData");
    return [UIImage imageWithData:data];
}

- (id)retain {
    NSLog(@"UIImage retain: %d", [self retainCount]);
    return [super retain];
}

- (oneway void)release {
    NSLog(@"UIImage release: %d", [self retainCount]);
    [super release];
}

- (id)autorelease {
    NSLog(@"UIImage autorelease: %d", [self retainCount]);
    return [super autorelease];
}

- (void)dealloc {
    NSLog(@"UIImage deallocated");
    [super dealloc];
}

@end

我的缓存代码:

.h

#import "BaseUIimage.h"

@interface UIImageCached : BaseUIimage

// CACHE
+ (NSMutableDictionary*) cache;
+ (void) cleanCache;
+ (UIImageCached *)imageNamed:(NSString *)imageName;
+ (UIImageCached *)retinaImageNamed:(NSString *)imageName;
+ (UIImageCached *)imageFromPath:(NSString *)imagePath;

.m

#import "UIImageCached.h"

@implementation UIImageCached

static NSMutableDictionary *data;

+ (NSMutableDictionary*) cache {
    if (data == nil)
        data = [[NSMutableDictionary alloc] initWithCapacity:150];
    return data;
}

+ (void) cleanCache {
    NSLog(@"Cache cleaned images: %d", data.count);

    for (BaseUIimage *image in data) {
        NSLog(@"image rc: %d", [image retainCount]); // always prints rc = 1
    }

    [data removeAllObjects];
}

+ (UIImageCached *)imageFromPath:(NSString *)imagePath {
    UIImageCached *image = (UIImageCached*)[self.cache objectForKey:imagePath];
    if (image == nil) {
        NSData *imageData = [[NSData alloc] initWithContentsOfFile:imagePath options:NSDataReadingMappedIfSafe error:nil];
        image = (UIImageCached*)[UIImageCached imageWithData:imageData];
        [imageData release];

        if (image) {
            [self.cache setObject:image forKey:imagePath];
            //NSLog(@"new cached image: #%d", self.cache.count);
        } else {
            //NSLog(@"can't cache image: #%d", self.cache.count);
        }
    }

    return image;
}

+ (UIImageCached *)imageNamed:(NSString *)imageName {
    NSString *extension = [imageName pathExtension];
    NSString *fileName = [imageName stringByDeletingPathExtension];
    NSString *fileLocation = [[NSBundle mainBundle] pathForResource:fileName ofType:extension];
    return [self imageFromPath:fileLocation];
}

+ (UIImageCached *)retinaImageNamed:(NSString *)imageName {
    UIImageCached *image = (UIImageCached*)[self.cache objectForKey:imageName];
    if (image == nil) {
        NSString *extension = [imageName pathExtension];
        NSString *fileName = [imageName stringByDeletingPathExtension];

        float s = 1.0;

        // retina filename support
        if(!isIPAD && [[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
            s = [[UIScreen mainScreen] scale];
            if (s > 1)
                fileName = NSTR2(@"%@%@", fileName, @"@2x");            
        }

        NSString *fileLocation = [[NSBundle mainBundle] pathForResource:fileName ofType:extension];

        NSData *imgData = [[NSData alloc] initWithContentsOfFile:fileLocation options:NSDataReadingMappedIfSafe error:nil];

        BaseUIimage *tmpImage = [[BaseUIimage alloc] initWithData:imgData];

        [imgData release];

        image = (UIImageCached*)[UIImageCached imageWithCGImage:tmpImage.CGImage 
                                                    scale:s 
                                              orientation:UIImageOrientationUp];        

        [tmpImage release];

        if (image) {
            [self.cache setObject:image forKey:imageName];
            //NSLog(@"-- CACHE: new cached image: #%d", self.cache.count);          
        } else {
            NSLog(@"-- CACHE: can't cache image: %@", fileLocation);
        }
    } else {
        //NSLog(@"-- CACHE: read cached image");
    }
    return image;
}

@end

为什么从不调用 release 和 dealloc?这是否意味着我创建的 UIImage 实例没有被释放,这就是虚拟内存增长的原因?

【问题讨论】:

  • 你曾经调用过 cleanCache 吗?否则缓存似乎会保留图像。
  • 是的。在 AppDelegate 的这个方法中: - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { NSLog(@"BTC_DashboardAppDelegate applicationDidReceiveMemoryWarning"); [UIImageCached cleanCache]; }

标签: objective-c ios memory memory-management uiimage


【解决方案1】:

Dave Wood 几乎是对的(他的实现仍然会创建一个 UIImage)。问题是

[UIImage imageWithData:data];

将创建一个 UIImage 而不是您期望它创建的子类。要对此进行测试,请尝试以下 imageWithData 实现:

+ (id)imageWithData:(NSData *)data {
    NSLog(@"UIImage imageWithData");
    UIImage *im = [UIImage imageWithData:data];
    NSLog(@"%@",[im class]);
    return im;
}

NSLog 会输出 UIImage。不是你需要的。我建议以下实现:

+ (id)imageWithData:(NSData *)data {
    NSLog(@"UIImage imageWithData");
    BaseUIimage *im = [[BaseUIimage alloc] initWithData:data];
    return [im autorelease];
}

这将创建 BaseUIimage 类的图像,从而表现出您的预期。干杯。

【讨论】:

  • 谢谢!但是为什么以前的实现不起作用?例如,当我将 UIViewController 子类化时它正在工作。
  • 我不明白你如何使用 UIViewController 子类。然而,[UIImage imageWithData:data] 将创建一个 UIImage 类的对象,无论你在哪里调用它。没有办法解决它。当您调用 [UIImage imageWithData:data] 时,您是在说“嘿 UIImage 类为我创建一个包含此数据的实例。” UIImage 只知道 UIImage 而不是子类。那个“imageWithData:”是一个类工厂方法。当您执行 [[BaseUIimage alloc] initWithData:data] 调用 BaseUIimage 从 UIImage 继承的构造函数时,情况会有所不同。现在的问题是,我的建议解决了你的问题吗?
【解决方案2】:

您创建了错误的子类。在这里的代码中:

@implementation BaseUIimage

+ (id)imageWithData:(NSData *)data {
    NSLog(@"UIImage imageWithData");
    return [UIImage imageWithData:data];
}

...

你没有得到 BaseUIimage 的实例,你得到的是一个常规的 UIImage,这意味着你覆盖的 release/dealloc 等不会被调用,因为它们不是 UIImage 类的一部分。

您需要将该函数更改为:

@implementation BaseUIimage

+ (id)imageWithData:(NSData *)data {
    NSLog(@"UIImage imageWithData");
    return [super imageWithData:data];
}

...

这将返回您的 BaseUIimage 类的实例。现在您将能够看到被覆盖的方法被调用了。

【讨论】:

  • 这肯定会导致无限递归。不是吗?
  • 是的,抱歉,来晚了。我已经编辑了答案,使用 super 代替。
猜你喜欢
  • 1970-01-01
  • 2012-10-27
  • 2013-10-12
  • 2012-03-27
  • 2013-08-29
  • 2013-11-03
  • 1970-01-01
  • 1970-01-01
  • 2015-05-06
相关资源
最近更新 更多