1

アプリが大量のメモリを使用し、いくつかのメモリ警告の後にクラッシュする理由を理解しようとしています。Instrument VM Tracker は、Dirty メモリとして約 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 インスタンスが割り当て解除されていないということですか?それが仮想メモリの増加の理由ですか?

4

2 に答える 2

4

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 クラスのイメージが作成され、期待どおりに動作します。乾杯。

于 2012-04-29T13:56:37.267 に答える
3

サブクラスを間違って作成しました。ここのコードでは:

@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 クラスのインスタンスを返します。これで、オーバーライドされたメソッドが呼び出されるのを見ることができます。

于 2012-04-29T08:20:13.357 に答える