2

比較的大きな画像がメモリから解放されないように見えるという問題があります (サイズが 1MB ~ 5MB)。次のコード ブロックは、ユーザーが一連の画像をスクロールしているときに呼び出されます。約 15 枚の画像の後、アプリケーションはクラッシュします。「didReceiveMemoryWarning」が呼び出されることもあれば、呼び出されないこともあります。アプリケーションはクラッシュし、停止し、デバッグを終了し、コードのどの行でも停止しません。何も起こりません。これは、デバイスのメモリが不足したときに何が起こると思いますか? 別の懸念は、サブクラス化された「DownloadImageOperation」に対して「dealloc」が呼び出されないように見えることです。何か案は?

画像の取得と設定:

//Calling this block of code multiple times will eventually cause the
// application to crash

//Memory monitor shows real memory jumping 5MB to 20MB increments in instruments.  
//Allocations tool shows #living creeping up after this method is called.
//Leaks indicate something is leaking, but in the 1 to 5 kb increments. Nothing huge.

DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
    //Set the image in a UIImageView in the open UIViewController.
    [self.ivScrollView setImage:imageOp.image];
}];
//Add operation to ivar NSOperationQueue
[mainImageQueue addOperation:imageOp];
[imageOp release];

DownloadImageOperation 定義:

.h ファイル

#import <Foundation/Foundation.h>

@interface DownloadImageOperation : NSOperation {
    UIImage * image;
    NSString * downloadURL;
    NSString * downloadFilename;
}

@property (retain) UIImage * image;
@property (copy) NSString * downloadURL;
@property (copy) NSString * downloadFilename;

- (id) initWithURL:(NSString *)url localPath:(NSString *)filename;

@end

.m ファイル

#import "DownloadImageOperation.h"
#import "GetImage.h"

@implementation DownloadImageOperation

@synthesize image;
@synthesize downloadURL;
@synthesize downloadFilename;

- (id) initWithURL:(NSString *)url localPath:(NSString *)filename {

    self = [super init];

    if (self!= nil) {
        [self setDownloadURL:url];
        [self setDownloadFilename:filename];
        [self setQueuePriority:NSOperationQueuePriorityHigh];
    }

    return self;

}

- (void)dealloc { //This never seems to get called?
    [downloadURL release], downloadURL = nil;
    [downloadFilename release], downloadFilename = nil;
    [image release], image = nil;
    [super dealloc];
}

-(void)main{

    if (self.isCancelled) {
        return;
    }

    UIImage * imageProperty = [[GetImage imageWithContentsOfFile:downloadFilename andURL:downloadURL] retain];
    [self setImage:imageProperty];
    [imageProperty release];
    imageProperty = nil;
}

@end

画像クラスを取得

.m ファイル

+ (UIImage *)imageWithContentsOfFile:(NSString *)path andURL:(NSString*)urlString foundFile:(BOOL*)fileFound {

    BOOL boolRef;

    UIImage *image = nil;

    NSString* bundlePath = [[NSBundle mainBundle] bundlePath];

    if (image==nil) {
        boolRef = YES;
        image = [UIImage imageWithContentsOfFile:[[AppDelegate applicationImagesDirectory] stringByAppendingPathComponent:[path lastPathComponent]]];
    }
    if (image==nil) {
        boolRef = YES;
        image = [super imageWithContentsOfFile:path];
    }
    if (image==nil) {
        //Download image from the Internet
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];

        NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];

        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
        [request setTimeOutSeconds:120];
        [request startSynchronous];

        NSData *responseData = [[request responseData] retain];

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

        NSData *rdat = [[NSData alloc] initWithData:responseData];
        [responseData release];

        NSError *imageDirError = nil;
        NSArray *existing_images = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[path stringByDeletingLastPathComponent] error:&imageDirError];

        if (existing_images == nil || [existing_images count] == 0) {
            // create the image directory
            [[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:NO attributes:nil error:nil];
        }

        BOOL write_success = NO;
        write_success = [rdat writeToFile:path atomically:YES];

        if (write_success==NO) {
            NSLog(@"Error writing file: %@",[path lastPathComponent]);
        }

        image = [UIImage imageWithData:rdat];
        [rdat release];

    }

    return image;
}

この巨大なコード ブロックについてお詫び申し上げます。どこに問題があるのか​​ 本当にわからないので、できるだけ包括的にしようとしました. 読んでくれてありがとう。

4

1 に答える 1

9

imageOp割り当てが解除されない操作の主な問題は、完了ブロックでの参照によって発生する保持サイクルがあることです。次のようなコードを検討してください。

DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
    //Set the image in a UIImageView in the open UIViewController.
    [self.ivScrollView setImage:imageOp.image];
}];

ARC では、操作に修飾子を追加し、内で__weakはなくそれを使用して、強い参照サイクルを回避します。手動参照カウントでは、修飾子を使用して同じことを達成することで保持サイクルを回避できます。つまり、ブロックが保持されないようにすることができます。imageOpcompletionBlock__blockimageOp

DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
    //Set the image in a UIImageView in the open UIViewController.
    [self.imageView setImage:blockImageOp.image];
}];

そうすれば、操作が正しくリリースされることがわかると思います。( Transitioning to ARC Release Notesの「Use Lifetime Qualifiers to avoid strong reference cycles」セクションを参照してください。ARC を使用していないことは承知していますが、このセクションでは ARC と手動の参照カウント ソリューションの両方について説明します。)


気にしない場合は、コードに関して他の観察がありました。

  • メイン キューにディスパッチせずにから UI を更新するべきではありませんcompletionBlock。すべての UI の更新はメイン キューで行う必要があります。

    DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
    __block DownloadImageOperation *blockImageOp = imageOp;
    [imageOp setCompletionBlock:^(void){
        //Set the image in a UIImageView in the open UIViewController.
        UIImage *image = blockImageOp.image;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [self.imageView setImage:image];
        }];
    }];
    
  • メソッドでアクセサー メソッドを使用していますinit。良い習慣として、実際にはすべきではありません。『 Advanced Memory Management Programming Guide』の「イニシャライザ メソッドでアクセサ メソッドを使用しない」および「dealloc」を参照してください。

  • 操作が解放されないという問題は修正されたかもしれませんが、表示されている画面からスクロールされた画像を解放する呼び出しを既にコーディングしていない限りUIScrollViewDelegate、引き続きメモリの問題が発生するのではないかと思います。そうは言っても、あなたはすでにこの問題に取り組んでいるかもしれません。この問題を修正するのは簡単なので、問題を提起するだけですNSOperationが、スクロールビューが画面からスクロールして画像を解放することを怠っています。

  • Concurrency Programming GuideのDefining a Custom Operationで説明されている主要なメソッドのいくつかが欠けているため、サブクラスNSOperationが同時実行をサポートするかどうかはわかりません. おそらく、すでにこれを行っているかもしれませんが、簡潔にするために省略しました。または、このような処理を行う既存のクラス (例: )のいずれかを使用する方が簡単だと思います。あなたの呼び出しですが、並行性を追求する場合は、キューを 4 などの適切な値に設定する必要があります。NSOperationNSBlockOperationmaxConcurrentOperationCount

  • コードには冗長なretainステートメントがいくつかあります。とは言っても、必要なrelease記載もあり、問題ないことは確認済みですが、ちょっと気になるところです。明らかに、ARC はその種の雑草から抜け出させてくれますが、これが大きな一歩であることを高く評価しています。しかし機会があれば、ARC を検討してみてください。

  • デッド ストアなどがあるため、コードを静的アナライザー ([製品] メニューの [分析]) で実行する必要があります。

于 2013-02-05T06:31:48.687 に答える