21

Apple のドキュメントによると、iOS7 アプリケーションには xcassets を使用し、imageNamed でこれらのイメージを参照することが推奨されています。

しかし、私の知る限り、常に imageNamed とメモリに問題がありました。

そこで、簡単なテスト アプリケーションを作成しました - imageNamed を使用して xcassets カタログから画像を参照し、プロファイラーを開始しました ... 結果は期待どおりでした。スーパービューからImageViewを削除してnilに設定した後でも、割り当てられたメモリは再び解放されませんでした。

私は現在、多くの大きな画像を含む iPad アプリケーションに取り組んでおり、この奇妙な imageView の動作により、メモリ警告が発生します。

しかし、私のテストでは、imageWithContentsOfFile 経由で xcassets イメージにアクセスできませんでした。

では、iOS7 で大きな画像を操作するための最良の方法は何ですか? 別の (より効率的な) 方法で xcassets カタログから画像にアクセスする方法はありますか? または、imageWithContentsOfFile を操作できるように、xcassets をまったく使用しないでください。

回答ありがとうございます。

4

1 に答える 1

16

更新:キャッシュの削除は正常に機能します (少なくとも iOS 8.3 以降)。

Apple の「新しい Images.xcassets」も使用することにしました。アプリに約 350 MB の画像があり、アプリが絶えずクラッシュしたとき、事態は悪化し始めました (Retina iPad では、おそらく読み込まれた画像のサイズが原因です)。

3 つの異なるタイプの画像をロードする (プロファイラーを監視する) 非常に単純なテスト アプリを作成しました。

  1. imageNamed:アセットからロード: 画像がリリースされず、アプリがクラッシュします (私にとっては 400 枚の画像をロードできましたが、実際には画像サイズに依存します)

  2. imageNamed:(従来はプロジェクトに含まれていました): メモリ使用量が高く、たまに (> 400 画像) への呼び出しが表示されますdidReceiveMemoryWarning:が、アプリは正常に動作しています。

  3. imageWithContentsOfFile([[NSBundle mainBundle] pathForResource:...): イメージは一度に 1 回しかロードされないため、メモリ使用量は非常に低くなります (<20MB)。

画像を何度も表示する必要がある場合、キャッシングは良い考えであるため、メソッドのキャッシングをすべてのせいにするつもりはありませんがimageNamed:、Apple が資産に対してそれを実装しなかった (または文書化しなかった) のはちょっと悲しいことです実装されていないこと)。imageWithData私のユースケースでは、ユーザーが画像を再び見ることはないため、非キャッシュを使用します。

私のアプリはほぼ最終版であり、適切な画像を自動的に見つけるための読み込みメカニズムの使用法が非常に気に入っているため、使用法をラップすることにしました。

  • project-target-copy-phase から images.xcasset を削除し、すべての画像をプロジェクトと copy-phase に「再度」追加しました (単に Images.xcassets の最上位フォルダーを直接追加し、チェックボックス「追加」 To Target xxx" がチェックされ、"Create groups for any added Folders" がチェックされています (役に立たない Contents.json ファイルについては気にしませんでした)。
  • 最初のビルド中に、複数のイメージが同じ名前 (および一貫した方法で名前を変更) の場合、新しい警告をチェックします。
  • アプリ アイコンと起動イメージについては、project-target-general で「アセット カタログを使用しない」を設定し、そこで手動で参照します。
  • すべての Contents.json ファイルから json-model を生成するシェル スクリプトを作成しました (Apple がアセット アクセス コードで使用する情報を取得するため)。

脚本:

cd projectFolderWithImageAsset
echo "{\"assets\": [" > a.json
find Images.xcassets/ -name \*.json | while read jsonfile; do
  tmppath=${jsonfile%.imageset/*}
  assetname=${tmppath##*/}
  echo "{\"assetname\":\"${assetname}\",\"content\":" >> a.json
  cat $jsonfile >> a.json; 
  echo '},' >>a.json
done
echo ']}' >>a.json
  • ここではわざわざ手動で行う必要がなかったため、json 出力から最後の「,」コンマを削除します。
  • 次のアプリを使用して json-model-access コードを生成しました: https://itunes.apple.com/de/app/json-accelerator/id511324989?mt=12 (現在は無料) 接頭辞 IMGA
  • 実行中のコードを変更しないようにするために、メソッドのスウィズリングを使用して適切なカテゴリを作成しました (そして、コードをすぐに削除できることを願っています)。

(すべてのデバイスとフォールバック メカニズムの実装は完了していません!!)

#import "UIImage+Extension.h"
#import <objc/objc-runtime.h>
#import "IMGADataModels.h"

@implementation UIImage (UIImage_Extension)


+ (void)load{
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        Method imageNamed = class_getClassMethod(class, @selector(imageNamed:));
        Method imageNamedCustom = class_getClassMethod(class, @selector(imageNamedCustom:));
        method_exchangeImplementations(imageNamed, imageNamedCustom);
    });
}

+ (IMGABaseClass*)model {
    static NSString * const jsonFile = @"a";
    static IMGABaseClass *baseClass = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *fileFilePath = [[NSBundle mainBundle] pathForResource:jsonFile ofType:@"json"];
        NSData* myData = [NSData dataWithContentsOfFile:fileFilePath];
        __autoreleasing NSError* error = nil;
        id result = [NSJSONSerialization JSONObjectWithData:myData
                                                    options:kNilOptions error:&error];
        if (error != nil) {
            ErrorLog(@"Could not load file %@. The App will be totally broken!!!", jsonFile);
        } else {
            baseClass = [[IMGABaseClass alloc] initWithDictionary:result];
        }
    });
    return baseClass;
}


+ (UIImage *)imageNamedCustom:(NSString *)name{

    NSString *imageFileName = nil;
    IMGAContent *imgContent = nil;
    CGFloat scale = 2;

    for (IMGAAssets *asset in [[self model] assets]) {
        if ([name isEqualToString: [asset assetname]]) {
            imgContent = [asset content];
            break;
        }
    }
    if (!imgContent) {
        ErrorLog(@"No image named %@ found", name);
    }

    if (is4InchScreen) {
        for (IMGAImages *image in [imgContent images]) {
            if ([@"retina4" isEqualToString:[image subtype]]) {
                imageFileName = [image filename];
                break;
            }
        }
    } else {
        if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
            for (IMGAImages *image in [imgContent images]) {
                if ([@"iphone" isEqualToString:[image idiom]] && ![@"retina4" isEqualToString:[image subtype]]) {
                    imageFileName = [image filename];
                    break;
                }
            }
        } else {
            if (isRetinaScreen) {
                for (IMGAImages *image in [imgContent images]) {
                    if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
                        imageFileName = [image filename];
                        break;
                    }
                }
            } else {
                for (IMGAImages *image in [imgContent images]) {
                    if ([@"universal" isEqualToString:[image idiom]] && [@"1x" isEqualToString:[image scale]]) {
                        imageFileName = [image filename];
                        if (nil == imageFileName) {
                            // fallback to 2x version for iPad unretina
                            for (IMGAImages *image in [imgContent images]) {
                                if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
                                    imageFileName = [image filename];
                                    break;
                                }
                            }
                        } else {
                            scale = 1;
                            break;
                        }
                    }
                }
            }
        }
    }

    if (!imageFileName) {
        ErrorLog(@"No image file name found for named image %@", name);
    }

    NSString *imageName = [[NSBundle mainBundle] pathForResource:imageFileName ofType:@""];
    NSData *imgData = [NSData dataWithContentsOfFile:imageName];
    if (!imgData) {
        ErrorLog(@"No image file found for named image %@", name);
    }
    UIImage *image = [UIImage imageWithData:imgData scale:scale];
    DebugVerboseLog(@"%@", imageFileName);
    return image;
}

@end
于 2014-05-02T22:54:15.637 に答える