web-dev-qa-db-ja.com

リークなしでiOSアプリのメモリ使用量を削減

iOSアプリのメモリ使用量は多いですが、メモリリークはありません。メモリ使用量を減らすにはどうすればよいですか。

Instrumentsを使用して、メモリ警告が発生する前にアプリが最大90 MBに達し、他のメモリの割り当てが解除された後、残りの使用量の間、約55〜65MBのままであることを発見しました。

55-65MBは高すぎると思いますよね?

以来、Instrumentsはリークをキャッチしませんでした。このメモリ使用量を減らすために何ができますか?

私は今年のWWDCビデオを読みましたが、私が理解したもの(これは私の最初のiOSアプリです)のうち、ほとんどがリークの処理をカバーしていました。

いくつかのおそらく有用な情報:

VM:ImageIO_GIF_Data30.35MBライブバイト| 115生活| 300トランジェント| 136.12MB全体のバイト

VM:MappedFile36.04MBライブバイト| 16生活| 11トランジェント| 36.09MB全体のバイト

他のすべてのものは1MB未満です

私のアプリはインターネットから約30個のGIFファイルをダウンロードし、SDWebImageを使用し、画像のURLを保存するだけで、残りはSDWebImageが行います。 :P

前もって感謝します、

IOSメモリ管理の最初のタイマーから


Here is a screenshot of what Instruments shows me

助けてくれてありがとう

21
GangstaGraham

メモリ節約のための完全なコードを追加することにしました。GIFファイルを使用している場合は、UIImageスケールメソッドを変更してください(ここにあります、Stackoverflow)。前述のようにGangstaGraham SDイメージには、メソッドsd_animatedImageByScalingAndCroppingToSizeが存在します

@interface UIImage (Scaling)

-(UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize;
-(UIImage*) croppedImageWithRect: (CGRect) rect;

@end

@implementation UIImage (Scaling)

- (UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize {

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        if ([[UIScreen mainScreen] scale] == 2.0) {
            targetSize.height *= 2.0f;
            targetSize.width *= 2.0f;
        }
    }

    NSUInteger width = targetSize.width;
    NSUInteger height = targetSize.height;
    UIImage *newImage = [self resizedImageWithMinimumSize: CGSizeMake (width, height)];
    return [newImage croppedImageWithRect: CGRectMake ((newImage.size.width - width) / 2, (newImage.size.height - height) / 2, width, height)];
}

-(CGImageRef)CGImageWithCorrectOrientation
{
    if (self.imageOrientation == UIImageOrientationDown) {
        //retaining because caller expects to own the reference
        CGImageRetain([self CGImage]);
        return [self CGImage];
    }

    UIGraphicsBeginImageContext(self.size);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (self.imageOrientation == UIImageOrientationRight) {
        CGContextRotateCTM (context, 90 * M_PI/180);
    } else if (self.imageOrientation == UIImageOrientationLeft) {
        CGContextRotateCTM (context, -90 * M_PI/180);
    } else if (self.imageOrientation == UIImageOrientationUp) {
        CGContextRotateCTM (context, 180 * M_PI/180);
    }

    [self drawAtPoint:CGPointMake(0, 0)];

    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    UIGraphicsEndImageContext();

    return cgImage;
}

-(UIImage*)resizedImageWithMinimumSize:(CGSize)size
{
    CGImageRef imgRef = [self CGImageWithCorrectOrientation];
    CGFloat original_width  = CGImageGetWidth(imgRef);
    CGFloat original_height = CGImageGetHeight(imgRef);
    CGFloat width_ratio = size.width / original_width;
    CGFloat height_ratio = size.height / original_height;
    CGFloat scale_ratio = width_ratio > height_ratio ? width_ratio : height_ratio;
    CGImageRelease(imgRef);
    return [self drawImageInBounds: CGRectMake(0, 0, round(original_width * scale_ratio), round(original_height * scale_ratio))];
}

-(UIImage*)drawImageInBounds:(CGRect)bounds
{
    UIGraphicsBeginImageContext(bounds.size);
    [self drawInRect: bounds];
    UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resizedImage;
}

-(UIImage*)croppedImageWithRect:(CGRect)rect
{

    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect drawRect = CGRectMake(-rect.Origin.x, -rect.Origin.y, self.size.width, self.size.height);
    CGContextClipToRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
    [self drawInRect:drawRect];
    UIImage* subImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return subImage;
}

-(UIImage *) resizableImageWithCapInsets2: (UIEdgeInsets) inset
{
    if ([self respondsToSelector:@selector(resizableImageWithCapInsets:resizingMode:)])
    {
        return [self resizableImageWithCapInsets:inset resizingMode:UIImageResizingModeStretch];
    }
    else
    {
        float left = (self.size.width-2)/2;//The middle points rarely vary anyway
        float top = (self.size.height-2)/2;
        return [self stretchableImageWithLeftCapWidth:left topCapHeight:top];
    }
}

@end

そしてUIImageView:

#import <SDWebImage/SDImageCache.h>

@implementation UIImageView (Scaling)

-(void)setImageWithURL:(NSURL*)url scaleToSize:(BOOL)scale
{
    if(url.absoluteString.length < 10) return;
    if(!scale){
        [self setImageWithURL:url];
        return;
    }
    __block UIImageView* selfimg = self;
    __block NSString* prevKey = SPRINTF(@"%@_%ix%i", url.absoluteString, (int)self.frame.size.width, (int)self.frame.size.height);
    __block UIImage* prevImage = nil;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^ {

        prevImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:prevKey];
        if(prevImage){
            dispatch_async(dispatch_get_main_queue(), ^ {
                [self setImage:prevImage];
            });
        }else{

            [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:url options:SDWebImageDownloaderFILOQueueMode progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                if(error){
                    [selfimg setImageWithURL:url scaleToSize:scale];
                }else{
                    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
                    dispatch_async(queue, ^ {
                        prevImage = [image imageByScalingProportionallyToSize:self.frame.size];
                        if(finished)
                            [[SDImageCache sharedImageCache] storeImage:prevImage forKey:prevKey];
                        dispatch_async(dispatch_get_main_queue(), ^ {
                            [self setImage:prevImage];
                        });
                    });
                }
            }];
        }
    });

    return;
}

-(void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder scaleToSize:(BOOL)scale
{
    [self setImage:placeholder];
    [self setImageWithURL:url scaleToSize:scale];
}


@end
1
HotJard

テーブルビューを使用していると言います。セルは自動的に再利用されますが、これにより、間違いを犯しやすくなり、オブジェクトが多すぎます。 1つの一般的なエラーは、cellForRowAtIndexPathメソッドでのオブジェクト(UIImageViewなど)の割り当てです。これは、セルが再利用されるたびに、新しいUIImageViewが追加され、古いものが保持されることを意味します。したがって、cellForRowAtIndexPathメソッドで何が起こっているかを再確認してください。

4
Darren

Instruments and HeapshotAnalysisを使用することをお勧めします。 bbumはそれについての記事を 彼のブログ に書いた。

概要は次のとおりです。

  1. Instrumentsでアプリを起動し、割り当てテンプレートを選択します
  2. アプリが落ち着き始めてからしばらく待ちます
  3. [割り当て]で、Mark Heapを押します。これがベースラインです。
  4. アプリを使用して、#2と同じ画面に戻ります。もう一度Mark Heapを押します。
  5. それをしばらく繰り返します。

メモリが着実に増加している場合は、ヒープショットをドリルダウンして、割り当てられているすべてのオブジェクトを確認できます。それはあなたにあなたのメモリフットプリントを減らすための良いスタートを与えるはずです。

1
mkalmes

SDWebImageは残りを行いません。できるだけ少ないメモリ内の画像を処理する必要があります。表示されていないときにUIImageViewを消去します。再利用可能なオブジェクトパターンを使用します。もちろん、メモリの警告が表示された場合は、表示されていない(メモリにキャッシュされている)画像をクリアします。これには、self.urImage = nil;を使用します。

だから、アプリのメモリ節約をよく見てください;)

0
HotJard