11

大きなUIScrollViewがあり、そこに3〜4個のかなり大きな(320x1500ピクセル程度)UIImageView画像タイルを配置しています。これらのUIImageViewをNIBファイル内のスクロールビューに追加しています。コントローラに1つのコンセントがあり、それはUIScrollViewへのコンセントです。このためにプロパティ(非アトミック、保持)を使用し、それを合成しています。

私の質問はこれです:メモリモニターでこれを観察すると、これらすべての画像を含むビューがロードされると、使用されるメモリがかなり増加することがわかります(予想どおり)。しかし、私がビューを離れると、それとそのコントローラーは割り当て解除されますが、それらが使用したメモリの近くではあきらめないようです。これらのビューの1つ(アプリにはいくつかあります)を320x460の1〜3枚の画像に切り取り、他のすべてを同じままにすると、メモリが正常に再キャプチャされます。

これほど大きな画像を使用することに問題はありますか?このコード(以下に貼り付け)で何か間違ったことをしていますか?

これは、問題を引き起こしているviewControllerからのスニペットです。

- (CGFloat)findHeight 
{
    UIImageView *imageView = nil;
    NSArray *subviews = [self.scrollView subviews];

    CGFloat maxYLoc = 0;
    for (imageView in subviews)
    {
            if ([imageView isKindOfClass:[UIImageView class]])
            {
                    CGRect frame = imageView.frame;

                    if ((frame.origin.y + frame.size.height) > maxYLoc) {
                            maxYLoc  = frame.origin.y;
                            maxYLoc += frame.size.height;
                    }
            }
    }
    return maxYLoc;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.scrollView setContentSize:CGSizeMake(320, [self findHeight])];

    [self.scrollView setCanCancelContentTouches:NO];
    self.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    self.scrollView.clipsToBounds = YES;                
    self.scrollView.scrollEnabled = YES;

    self.scrollView.pagingEnabled = NO;
}

- (void)dealloc {
    NSLog(@"DAY Controller Dealloc'd");
    self.scrollView = nil;
    [super dealloc];
}

更新:私は別の奇妙な現象に気づきました。ビューのスクロールを使用しない場合、それはメモリにぶら下がっているようです。しかし、たくさんスクロールして、すべてのUIImageViewが一度に表示されるようにすると、失われたメモリのほとんどが解放されて回復します。

UPDATE2:私がこれを求めている理由は、私のアプリがメモリ不足のために実際にクラッシュしているためです。キャッシュして余分なメモリを使い果たしただけでもかまいませんが、didReceiveMmoryWarningの状態であっても、解放されることはないようです。

4

7 に答える 7

15

私は謎を解きました-そしてこれはApple側のバグだと確信しています。

Kendallが提案したように(ありがとう!)、問題はInterfaceBuilderがNIBファイルから画像をロードする方法にあります。initFromNibを実行すると、すべてのUIImageViewは、UIImageのimageNamed:メソッドを使用してUIImageで初期化されます。この呼び出しは、画像のキャッシュを使用します。通常、これはあなたが望むものです。ただし、非常に大きな画像に加えて、表示領域から遠くにスクロールする画像では、メモリの警告に従わず、このキャッシュをダンプしていないようです。これは私がApple側のバグだと信じているものです(同意する/同意しない場合はコメントしてください-他の人が同意する場合はこれを提出したいと思います)。上で述べたように、これらの画像で使用されていたメモリは、ユーザーがすべてを表示できるように十分にスクロールすると解放されるようです。

私が見つけた回避策(Kendallの提案も)は、NIBファイルで画像名を空白のままにすることです。したがって、UIImageView要素を通常どおりにレイアウトしますが、画像は選択しないでください。次に、viewDidLoadコードで、代わりにimageWithContentsOfFile:を使用して画像を読み込みます。この方法では画像がキャッシュされないため、大きな画像を保持する際にメモリの問題が発生することはありません。

もちろん、imageNamed:は、パスを見つける必要がなく、デフォルトでバンドル内のすべてのものに設定されるため、はるかに使いやすくなっています。ただし、次の方法でバンドルへのパスを取得できます。

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

それをすべてまとめると、コードでは次のようになります。

NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]];
UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath];
imageView.image = loadImage;

したがって、上記のコードにそれを追加すると、完全な関数は次のようになります。

- (CGFloat)findHeight 
{
    UIImageView *imageView = nil;
    NSArray *subviews = [self.scrollView subviews];

    CGFloat maxYLoc = 0;
    for (imageView in subviews)
    {
        if ([imageView isKindOfClass:[UIImageView class]])
        {
            CGRect frame = imageView.frame;

            if ((frame.origin.y + frame.size.height) > maxYLoc) {
                maxYLoc  = frame.origin.y;
                maxYLoc += frame.size.height;
            }

            NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]];
            NSLog(fullpath);


            UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath];
            imageView.image = loadImage;
        }
    }
    return maxYLoc;
}
于 2008-11-14T20:08:11.507 に答える
4

imageNamedのキャッシュの問題とは別に、質問への回答

大きなUIScrollViewがあり、そこに3〜4個のかなり大きな(320x1500ピクセル程度)UIImageView画像タイルを配置しています。[...]これほど大きな画像の使用に問題はありますか?

UIImageドキュメントのヘッダーにあります:

You should avoid creating UIImage objects that are greater than 1024 x 1024 in size.
Besides the large amount of memory such an image would consume, you may run into 
problems when using the image as a texture in OpenGL ES or when drawing the image 
to a view or layer.

また、Appleは、3.0以降でimageNamedのキャッシュフラッシュの問題を修正したと主張していますが、私自身はこれを広範囲にテストしていません。

于 2009-10-23T18:57:36.423 に答える
4

画像への参照をメモリにキャッシュしているシステムである可能性があります.IBのメディアブラウザから画像をドラッグしたと仮定すると、その下のコードはおそらくUIImage imageNamed:メソッドを使用しています...

imageWithContentsOfFile: 、または: を使用してすべての画像を読み込んでみて、同じように動作するかどうかを確認できます ( IBに未入力でimageWithDataドラッグし、: にコンテンツを設定します) 。UIImageViewsviewDidLoad

UIImageもう少し詳しく知りたい場合は、クラス リファレンスを読んでください。どのメソッドがキャッシュされるかについても説明されています。

それがキャッシュである場合は、必要に応じてシステムが解放するため、おそらく問題ありません (シミュレーターでメモリのシミュレート警告も試しましたか?)

于 2008-11-14T07:12:26.160 に答える
1

Bdebeezの回答を完成させます。

imageNamed:1 つの良いアイデアは、呼び出しをオーバーライドすることimageWithContentsOfFile:です。

コードの考え方は次のとおりです。

@implementation UIImage(imageNamed_Hack)

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

    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], name ] ];
}

@end

注: このオーバーライドを使用すると、UIImage をロードするキャッシュがなくなります。これが必要な場合は、独自のキャッシュを実装する必要があります。

于 2009-11-10T18:56:45.263 に答える
0
- (void)dealloc {
    NSLog(@"DAY Controller Dealloc'd");
    [self.scrollView release];
    [super dealloc];
}

そのショットを与えると、@ property()定義はそれを保持するように要求していますが、オブジェクトを明示的に解放していません

于 2008-11-14T06:30:22.703 に答える
0

メモリ管理の動作について学んだことから、メモリが少なくない限り、ビューは解放されないということです。SQLBooks のような公式デモを試してください。 1. Leaks Monitor で実行します。 2. すべてのビューを実行します。3. ルート ビューに戻ります。4. メモリ使用量レベルが同じであることがわかります。ケンドールが言ったように、キャッシュされる可能性がありますか?

このメモリ使用パターンには注意を払うべきではないと思います。新しい画像が UIScrollView を指すと、古い画像オブジェクトが解放され、新しい画像のためにメモリが解放されるためです。

于 2008-11-14T08:57:31.160 に答える
0

imageName でのメモリ リークという既知の問題があります。アプリケーション デリゲートでイメージ キャッシュを作成し、アプリケーションのパフォーマンスとメモリ使用量を最適化するという、非常に便利な解決策を見つけました。 このブログ投稿を参照してください

于 2009-08-26T21:27:47.307 に答える