15

私は最近 ARC を使い始めましたが、それ以来、すべてのメモリの問題を ARC のせいにしています。:) おそらく、あなたは私が間違っていることをよりよく理解するのを助けることができます.

私の現在のプロジェクトは CoreGraphics に関するもので、グラフの描画、サムネイルで満たされたビューなどです。いくつかのゾンビを除いて、手動のメモリ管理を使用しても問題はないと思います...しかし、今のところ、大量のサムネイルを作成したり、もう少し複雑なチャートを再描画しようとすると、アプリケーションがクラッシュするだけです.

Instruments を使用してプロファイリングを行っていると、常駐メモリとダーティ メモリの値が非常に高いことがわかります。ヒープ分析は、かなり驚くべき不規則な成長を示しています...

ほんの数枚のサムネイルを描画すると、常駐メモリが約 200 MB 増加します。すべてが描画されると、メモリは描画前とほぼ同じ値に戻ります。ただし、多くのサムネイルがあると、常駐メモリの値が400 MBを超え、明らかにアプリがクラッシュします。同時に描画されるサムネイルの数 (NSOperationQueue とその maxConcurrentOperationCount) を制限しようとしましたが、大量のメモリを解放するにはもう少し時間がかかるように思われるため、実際には問題は解決しませんでした。

現在、実際のデータは多くの複雑なチャート = 多くのサムネイルで機能するため、私のアプリは基本的に機能しません。

すべてのサムネイルは、ここから取得したこのコードで作成されます: (UIImage のカテゴリ)

+ (void)beginImageContextWithSize:(CGSize)size
{
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        if ([[UIScreen mainScreen] scale] == 2.0) {
            UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
        } else {
            UIGraphicsBeginImageContext(size);
        }
    } else {
        UIGraphicsBeginImageContext(size);
    }
}

+ (void)endImageContext
{
    UIGraphicsEndImageContext();
}

+ (UIImage*)imageFromView:(UIView*)view
{
    [self beginImageContextWithSize:[view bounds].size];
    BOOL hidden = [view isHidden];
    [view setHidden:NO];
    [[view layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    [view setHidden:hidden];
    return image;
}

+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize
{
    UIImage *image = [self imageFromView:view];
    if ([view bounds].size.width != newSize.width ||
        [view bounds].size.height != newSize.height) {
        image = [self imageWithImage:image scaledToSize:newSize];
    }
    return image;
}

+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
    [self beginImageContextWithSize:newSize];
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    return newImage;
}

それほど多くのメモリを消費しない他の方法はありますか、または ARC を使用するときにコードに何か問題がありますか?

メモリ警告 + クラッシュが発生するもう 1 つの場所は、ビューの再描画が多すぎる場合です。速くする必要はありません。何度でも構いません。クラッシュするまでメモリが積み重なっていきますが、その原因は何かを見つけることができません。(VM Tracker で常駐/ダーティ メモリが増加し、Allocations インストゥルメントでヒープが増加していることがわかります)

私の質問は基本的に次のとおりです。なぜそれが起こっているのかを見つける方法は? 私の理解では、特定のオブジェクトの所有者がいない場合、できるだけ早くリリースされます。私のコードの調査では、発生する理由が見当たりませんが、多くのオブジェクトがまったく解放されていないことが示唆されています。保持サイクルについてはわかりません...

ARC リリース ノートへの移行、ヒープ分析に関する bbum の記事、およびおそらく他の多くの記事を読みました。ARC がある場合とない場合で、ヒープ分析がどうにか異なりますか? 私はその出力で何も役に立たないようです。

アイデアをありがとう。

更新: (全員にすべてのコメントを強制的に読ませず、約束を守るため)

私のコードを注意深く調べて、意味のある場所に @autoreleasepool を追加することで、メモリ消費量が削減されました。最大の問題はUIGraphicsBeginImageContext、バックグラウンド スレッドからの呼び出しでした。それを修正した後(詳細については@Tammo Freeseの回答を参照)、アプリをクラッシュさせないほどすぐに割り当て解除が行われました。

2 回目のクラッシュ (同じチャートを何度も再描画したことが原因) CGContextFlush(context)は、描画メソッドの最後に追加することで完全に解決されました。私を恥じてください。


似たようなことをしようとしている人への小さな警告: OpenGL を使用してください。CoreGraphics は、特に iPad 3 では、大きな描画をアニメーション化するのに十分な速度ではありません。

4

3 に答える 3

18

質問に答えるには:ARCでのメモリ警告とクラッシュの問題の特定は、基本的に手動保持解放(MRR)の場合と同じように機能します。ARCはを使用しretain、 MRRreleaseautorelease同様に、呼び出しを挿入するだけであり、場合によってはメモリ消費量をさらに削減する最適化がいくつか実施されています。

あなたの問題について:

投稿したInstrumentsのスクリーンショットには、割り当ての急増が見られます。これまでに遭遇したほとんどの場合、これらのスパイクは、自動解放されたオブジェクトが長すぎるためにぶら下がっていたことが原因でした。

  1. を使用するとおっしゃいNSOperationQueueました。をオーバーライドする場合-[NSOperationQueue main]は、メソッドのコンテンツ全体をでラップするようにしてください@autoreleasepool { ... }。自動解放プールはすでに配置されている可能性がありますが、保証されているわけではありません(また、自動解放プールが存在する場合でも、思ったよりも長く存在する可能性があります)。

  2. 1.が役に立たず、画像を処理するループがある場合は、ループの内側をラップして@autoreleasepool { ... }、一時オブジェクトがすぐにクリーンアップされるようにします。

  3. を使用するとおっしゃいNSOperationQueueました。iOS 4以降、UIKitでグラフィックスコンテキストに描画することはスレッドセーフですが、ドキュメントが正しければUIGraphicsBeginImageContext、メインスレッドでのみ呼び出す必要があります。更新:iOS 4以降、関数は任意のスレッドから呼び出すことができるとドキュメントに記載されていますが、実際には次のものは不要です。安全のために、でコンテキストを作成し、でCGBitmapContextCreate画像を取得しますCGBitmapContextCreateImage。これらの線に沿った何か:

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    
    // draw to the context here
    
    CGImageRef newCGImage = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    UIImage *result = [UIImage imageWithCGImage:newCGImage scale:scale orientation: UIImageOrientationUp];
    CGImageRelease(newCGImage);
    
    return result;
    
于 2012-07-27T16:49:53.043 に答える
2

これはあなたの質問への回答ではありませんが、ARC が導入されるずっと前から同様の問題を解決しようとしていました。最近、メモリに画像をキャッシュし、メモリ警告を受け取った後にそれらをすべて解放するアプリケーションに取り組んでいました。これは、アプリケーションを通常の速度で使用している限り(クレイジーなタッピングなし)、問題なく機能しました。しかし、多くのイベントを生成し始め、多くの画像がロードされ始めたとき、アプリケーションはメモリ警告を受け取ることができず、クラッシュしていました。

ボタンをタップした後に多くの自動解放オブジェクトを作成するテスト アプリケーションを作成したことがあります。OSがメモリを解放するよりも速くタップする(そしてオブジェクトを作成する)ことができました。メモリはゆっくりと増加していたので、かなりの時間が経過した後、または単に大きなオブジェクトを使用しただけで、アプリケーションがクラッシュし、デバイスが再起動しました(非常に効果的です;))。残念ながらテストに影響を与え、すべてを遅くするインストゥルメントを使用していることを確認しましたが、これはインストゥルメントを使用しない場合にも当てはまると思います。

別の機会に、私は非常に複雑で、コードから作成された多くの UI を含む、より大きなプロジェクトに取り組んでいました。また、多くの文字列処理があり、リリースを使用することを気にする人は誰もいませんでした.前回チェックしたとき、自動リリース呼び出しは数千回でした. そのため、このアプリケーションを 5 分間使用した後、デバイスがクラッシュして再起動しました。

私が正しければ、実際にメモリの割り当てを解除することを担当する OS/ロジックは、多くのメモリ操作が実行されたときにアプリケーションがクラッシュするのを防ぐのに十分な速度ではないか、または優先度が十分に高くありません。これらの疑いを確認したことはなく、割り当てられたメモリを単に減らす以外にこの問題を解決する方法がわかりません。

于 2012-07-25T11:59:00.513 に答える
2

したがって、メモリ管理に関連して行っていることは何もありません (何もありません!) 不適切に見えます。ただし、NSOperationQueueの使用について言及しています。これらの UIGraphics... 呼び出しはスレッドセーフではないとマークされていますが、iOS 4 の時点であると述べている人もいます (これに対する決定的な答えは見つかりませんが、これは本当であることを思い出してください。

いずれにしても、これらのクラス メソッドを複数のスレッドから呼び出すべきではありません。シリアル ディスパッチ キューを作成し、それを介してすべての作業をフィードして、シングル スレッドの使用を保証することができます。

もちろん、ここで欠けているのは、画像を使用した後に画像をどうするかということです。明らかではない何らかの方法でそれらを保持している可能性があります。ここにいくつかのトリックがあります:

  • 多くの画像を使用するクラスのいずれかで、その名前といくつかの識別子をログに記録する dealloc() メソッドを追加します。

  • UIImage に dealloc を追加して、同じことを試みることができます。

  • 画像とその所有者が実際に解放されていることを確認できるように、可能な限り単純な設定 (画像を最小限にするなど) を使用してアプリを実行してみてください。

  • 何かが確実に解放されるようにしたい場合は、ivar またはプロパティを nil に設定します。

昨年の夏、100 ファイルのプロジェクトを ARC に変換しましたが、そのままで完璧に動作しました。私はいくつかのオープン ソース プロジェクトを ARC に変換しましたが、ブリッジを不適切に使用したときに発生した問題は 1 つだけでした。技術は盤石です。

于 2012-07-24T13:42:32.617 に答える