0

アプリ全体で画像を簡単にロードできるように作成したヘルパー クラスがあります。

@implementation Helpers

+(UIImage *) getThumbnailImageIfExists:(NSString *)ItemSKU withManufacturer: (NSNumber *) aManufacturerID { 

    @autoreleasepool {

    NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:ItemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]];
    NSData *imageData = [NSData dataWithContentsOfFile:savePath];

    if  (imageData==nil) 
    {
         return nil;
    }

    return [UIImage imageWithData:imageData];

    }
}

@end

プロファイラーを使用して、アプリがクラッシュし続ける理由を確認しています。Leaks ツールと Heapshots を使用して、放棄されたメモリに何がぶら下がっているかを確認しています。これは私を殺しているようです。

この方法を修正するにはどうすればよいですか? これは、ARC に変換された古いプロジェクトです。

何かご意見は?

ここに画像の説明を入力

ここに画像の説明を入力

4

1 に答える 1

1

自動解放プール内に自動解放されたオブジェクト ( imageWithData) を作成し、それを返しますが、すぐにプールを空にします。最も簡単な修正は、その自動解放プールを削除することです。なぜそのプールがあるのですか?すぐに排水するだけNSDataですか?NSDataただし、画像を直接取得できるため、それはまったく必要ありません。

@implementation Helpers

+ (UIImage *) getThumbnailImageIfExists:(NSString *)ItemSKU withManufacturer: (NSNumber *) aManufacturerID { 

    NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:ItemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]];

    return [UIImage imageWithContentsOfFile:savePath];
}

@end

さまざまな文字列変数と配列変数 (つまり、fileNamepathsdocumentsPathおよびsavePath) が呼び出し元の自動解放プールに入れられないようにしたい場合は、その問題を解決できますが、それがどれほど重要かはわかりません (少なくともNSDataプールに入れられたものと比較して)。


次の代替実装を検討してください。

+ (UIImage *)getThumbnailImageIfExists:(NSString *)itemSKU withManufacturer:(NSNumber *)aManufacturerID
{
    UIImage *image;
    static NSString *documentsPath;
    static NSCache *cache;

    // create docsPath and cache once and only once

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        documentsPath = searchPaths[0];
        cache = [[NSCache alloc] init];
        cache.countLimit = 100;
    });

    // now do your image retrieval

    @autoreleasepool {
        NSString *fileName = [[[SharedFunctions sharedInstance] getLargeFileName:[aManufacturerID stringValue] withPhotoName:itemSKU] stringByReplacingOccurrencesOfString:@"_lg.jpg" withString:@"_tn.jpg"];
        NSString *savePath = [documentsPath stringByAppendingPathComponent:[fileName lowercaseString]];

        image = [cache objectForKey:savePath];
        if (!image)
        {
            image = [[UIImage alloc] initWithContentsOfFile:savePath]; // note, not an autoreleased object
            [cache setObject:image forKey:savePath];
        }
    }

    return image;
}

私はここでいくつかのことをしています:

  1. 前と同じように、不要なNSDataロジックを削除しました。ファイルを にロードして、そこから をNSData作成する必要はありません。UIImageNSData

  2. NSCacheこのイメージを同じ SKU/製造元に対して繰り返し呼び出している場合、ロードするイメージを保存するためにを使用することで、メモリを大幅に節約できます (パフォーマンスも向上します) 。同じ画像を複数回リクエストした場合に、重複する画像が作成されるのを防ぎます。を使用NSCacheすると、その問題が解決します。これNSCacheは便利なキーです (メーカー コードと SKU から構成された文字列を使用することもできますが、それはあなた次第です)。

  3. 私はdispatch_once2つの静的変数を設定するのに役立ちました:

    • (これを何万回も呼び出している場合は目に見える影響がありますが、documentsPathこれを数百回しか呼び出していない場合、改善はおそらく観察できないでしょう)

    • (そして、cacheこのメソッドへの呼び出しのインスタンス間でキャッシュを持続させたい場合は、staticそれを持続させるためにこれを作るなどのことをする必要がありますが、一度それを設定する必要がありますdispatch_once

    率直に言うと、シングルトン インスタンスのインスタンス変数としてdocumentsPathおよび/またはを移動し、これらの変数をを使用するのではなく、適切な方法で設定する傾向がありますが、あなたが私たちと共有した方法。cacheinitdispatch_once

  4. 本当にマイナーな変更ですが、私は常に変数名にキャメルケース (小文字で始まる) を使用しているためItemSKUitemSKU.

  5. 私はあなたのブロックを採用しましたが、たとえば、@autoreleasepoolこのメソッドを単一のループ内から何度も呼び出す場合を除き、通常は必要ありません。forこれらがテーブル ビューまたはコレクション ビューで使用されるサムネイルである場合、@autoreleasepoolブロックは必要ありません。しかし、これらの非常に特殊なシナリオのいずれかが当てはまる場合に備えて、そこに保管しておきました。

    個人的には、@autoreleasepool何らかの値を返すコードではなく、自己完結型のコード ブロックを囲むブロックを使用します。しかし、状況がそれを必要とする場合は、上記のようなことを行うことができます.

を使用するとcache、同じイメージに対してこのメ​​ソッドを複数回呼び出す場合、(メモリ消費とパフォーマンスの両方の点で) 大きな影響があります。staticdispatch_onceforの使用によるdocumentsPathパフォーマンスへの影響はわずかですが、これを頻繁に呼び出している場合は、その影響顕著になり、検討する必要があるかもしれません。

ブロックの使用は@autoreleasepool、メモリの増加が見られる場合に役立ちますが、後でそれが完了すると妥当なレベルに戻りますが、単にその「最高水準点」を減らしたいだけです。メモリがまったく低下しないことが問題である場合、自動解放プールは役に立ちません。問題は別のところにあります。

これを自分で試して、プロファイラーで実行し、パフォーマンスとメモリ使用量を確認してください。個人的には、一般的にキャッシュの使用に焦点を当てており、このメソッドの呼び出し方に特異な点がない限り (たとえば、1 回のループ@autoreleasepoolで何千回も呼び出している場合など) 、それほど心配する必要はありませんが、for考慮すべきこと。@autoreleaseほとんどのシナリオでは、ブロックではなくキャッシュを使用することで真のメリットが得られます。

于 2013-05-17T19:06:18.610 に答える