PDFのページのプレビュー画像を作成しようとしていますが、メモリの解放に問題があります。
問題を循環させる簡単なテストアルゴリズムを作成しましたアプリは40回目の反復近くでクラッシュします:
_NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:@"myPdf.pdf"];
CFURLRef url = CFURLCreateWithFileSystemPath( NULL, (CFStringRef)pdfPath, kCFURLPOSIXPathStyle, NO );
CGPDFDocumentRef myPdf = CGPDFDocumentCreateWithURL( url );
CFRelease (url);
CGPDFPageRef page = CGPDFDocumentGetPage( myPdf, 1 );
int i=0;
while(i < 1000){
UIGraphicsBeginImageContext(CGSizeMake(768,1024));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,CGRectMake(0, 0, 768, 1024));
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, 1024);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
// --------------------------
// The problem is here (without this line the application doesn't crash)
UIImageView *backgroundImageView1 = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];
// --------------------------
UIGraphicsEndImageContext();
[backgroundImageView1 release];
NSLog(@"Loop: %d", i++);
}
CGPDFDocumentRelease(myPdf);
_
上記の行はメモリリークを生成しているようですが、instrumentsはメモリの問題を示していません。
この種の間違いから逃れることはできますか?誰かが私をどのように説明できますか? PDFのプレビューを表示する他の方法はありますか?
[〜#〜]更新[〜#〜]
問題は、メソッドUIGraphicsGetImageFromCurrentImageContext()
によって作成されたUIImage
のリリースではなく、この自動リリースイメージで作成されたUIImageView
のリリースだと思います。
コード行を3つのステップに分割しました。
_UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIImageView *myImageView = [[UIImageView alloc] init];
[myImageView setImage: myImage]; // Memory Leak
_
1行目と2行目ではメモリリークが発生しないため、UIGraphicsGetImageFromCurrentImageContextメソッドは問題ではないと思います。
私も次のように試しましたが、問題は解決しません。
_UIImageView *myImageView = [[UIImageView alloc] initWithImage:myImage];
_
Autoreleaseプロパティを持つUIImageを含むUIImageViewのリリースでメモリリークがあると思います。
この スレッド で説明されているように、UIViewを継承するオブジェクトUIImageViewを書き込もうとしました。
このソリューションは機能しますが、あまりエレガントではありません。回避策です。メモリの問題を解決するオブジェクトUIImageViewを使用することをお勧めします。
問題はこれです:
UIGraphicsGetImageFromCurrentImageContext()
自動解放されたUIImage
を返します。自動解放プールは、コードが制御をrunloopに戻すまでこのイメージを保持しますが、これは長時間行いません。この問題を解決するには、while
ループのすべての反復(または数回の反復)で新しい自動解放プールを作成して排出する必要があります。
私はそれが古い質問であることを知っています、しかし私はちょうど数時間これの壁に頭をぶつけています。私のアプリでは繰り返し呼び出します
UIImage *image = UIGraphicsGetImageFromCurrentImageContext()
image = nilと呼んでも、ループ内ではメモリを保持します。アプリが解放する前にメモリを保持する期間はわかりませんが、アプリがメモリ警告を受け取ってクラッシュするのに十分な長さであることは確かです。
@autoreleasepoolのUIGraphicsGetImageFromCurrentImageContext()からの画像を呼び出す/使用するコードをラップすることで、最終的にそれを解決することができました。ので、私は持っています:
@autoreleasepool {
UIImage *image = [self imageWithView:_outputImageView]; //create the image
[movie addImage:image frameNum:i fps:kFramesPerSec]; //use the image as a frame in movie
image = nil;
}
それが誰かを助けるかもしれないことを願っています。
今後の参考のために、これを解決するために私が行ったことを示します(Swift 4でテスト済み)。
インターネットから(ユーティリティキューに)ダウンロードされた新しい画像ごとに、以下の関数を呼び出していました。自動解放プールを実装する前は、約100を処理した後にクラッシュしていました。
簡単にするために、resizeImage関数で、autoreleasepoolとリークしていた部分を除いて必要なコードを削除しました。
private func resizeImage(image: UIImage, toHeight: CGFloat) -> UIImage {
return autoreleasepool { () -> UIImage in
[...]
let newImage = UIGraphicsGetImageFromCurrentImageContext() //Leaked
UIGraphicsEndImageContext()
return newImage!
}
}
これがお役に立てば幸いです。
このコードはメインスレッドで実行されていますか? UIGraphicsGetImageFromCurrentImageContext( link )のドキュメントには、そのように実行する必要があると記載されています。
大きな画像での作業でも同じ問題が発生しました。画像にパディングを追加したり、フィルターを適用したりすると、画像が画面に表示されているときに解放せずに200MBのメモリが割り当てられました。
この問題を解決するための実用的な方法を見つけました:
extension UIImage {
func release() -> UIImage? {
guard let data = UIImageJPEGRepresentation(self, 1.0) else { return nil }
return UIImage(data: data)
}
}
UIImagePNGRepresentationでも試してみましたが、割り当ても解除されていないようです。
上記のすべての解決策を試してもメモリリークが発生する場合は、ディスパッチキューを使用しているかどうかを確認してください。その場合は、必ずautoreleaseFrequencyを.workItemに設定してください。または、内部に設定した自動解放プールは実行されません。
DispatchQueue(label: "imageQueue", qos: .userInitiated, autoreleaseFrequency: .workItem)
それがお役に立てば幸いです。ブロックを保持しているのがDispatchQueueであることにようやく気付くまで、何時間も私を悩ませてきました。