6 に答える
メモリの問題を解決するために Apple サポートに連絡し、iPad 3 のメモリ不足の警告について尋ねました。
- メモリの警告はメイン スレッドで配信されるため、アプリがメイン スレッドをブロックしている場合、アプリはメモリの警告を受け取りません。
- アプリがメイン スレッドをブロックしていない場合でも、アプリが強制終了されてメモリが解放される前にメモリ警告が配信されないほど、メモリ使用量が急激に増加する可能性があります。
- メモリの警告は、カーネルがさまざまなレベルのメモリ プレッシャ間を遷移するときにトリガーされます。このため、アプリがメモリ警告を受け取り、しばらくしてからメモリが使い果たされたときに強制終了されることがよくあります。最初のメモリ警告により、アプリを存続させるのに十分なメモリが解放されましたが、カーネルがより低いレベルのメモリ プレッシャに移行するには十分ではありませんでした。
このため、メモリの警告は、ハードウェアの状態に関する有用なデータとして、および特定のデバイスでアプリが使用する必要があるメモリの量に関する適切なガイドとして扱う必要がありますが、アプリを防止するためのツールとして依存するべきではありません。殺されることから。
たぶんこれが役立つ...
この問題は iOS 5.1.1 で修正されました。5.1 を使用しているユーザーのために、独自のメモリ ウォッチドッグを実装し、実際のメモリ警告が発行されたときに使用されるものと同様の通知を送信しました。
で初めてカテゴリを作成しましたUIApplication
。UIImage
これにより、キャッシュされた画像をアンロードするために (またはそのバッキング キャッシュが何であれ) リッスンする通知が投稿されます。
.h
@interface UIApplication (ForceLowMemory)
+ (void) forceUnload;
@end
.m
#import "UIApplication+ForceLowMemory.h"
@implementation UIApplication (ForceLowMemory)
+ (void)forceUnload {
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification
object:[UIApplication sharedApplication]];
}
@end
次に、次のようなメモリ マネージャー ウォッチドッグを作成しました。
.h
@interface ELMemoryManager : NSObject
- (void)startObserving;
- (void)stopObserving;
+ (ELMemoryManager*) sharedInstance;
@end
.m
#import "ELMemoryManager.h"
#import "UIApplication+ForceLowMemory.h"
#import <mach/mach.h>
@interface ELMemoryManager()
@property (nonatomic, retain) NSTimer *timer;
uint report_memory(void);
@end
#define MAX_MEM_SIZE 475000000
@implementation ELMemoryManager
@synthesize timer = timer_;
static ELMemoryManager* manager;
#pragma mark - Singleton
+ (void) initialize {
if (manager == nil) {
manager = [[ELMemoryManager alloc] init];
}
}
+ (ELMemoryManager*) sharedInstance {
return manager;
}
#pragma mark - Instance Methods
uint report_memory(void) {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&info,
&size);
if( kerr == KERN_SUCCESS ) {
return info.resident_size;
} else {
return 0;
}
}
- (void)startObserving {
if (!self.timer) {
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:@selector(checkMemory:) userInfo:nil repeats:YES];
self.timer = timer;
}
[self.timer fire];
}
- (void)stopObserving {
[self.timer invalidate];
self.timer = nil;
}
- (void)checkMemory:(id)sender {
uint size = report_memory();
if (size > MAX_MEM_SIZE) {
NSLog(@"we've busted the upper limit!!!");
[UIApplication forceUnload];
}
}
#pragma mark - Memory Management
- (void)dealloc {
[self.timer invalidate];
[timer_ release];
[super dealloc];
}
@end
問題は、画像がリリースされていないことだと思います。UIImageのドキュメント(および私の経験)によると、imageNamed:
ロードした画像をキャッシュします。したがって、ほとんど常に使用される小さなアイコンや画像に使用する必要がありますが、大きな画像や使用頻度の低い画像に使用することは一般的にお勧めできません。user499177が言うように、代わりに(メソッドを使用してパスを導出imageWithContentsOfFile:
できます)を使用する必要があります。[NSBundle mainBundle]
このメソッドは、システムキャッシュで指定された名前の画像オブジェクトを探し、そのオブジェクトが存在する場合はそのオブジェクトを返します。一致する画像オブジェクトがまだキャッシュにない場合、このメソッドは指定されたファイルから画像データをロードしてキャッシュし、結果のオブジェクトを返します。
以下は私の経験です。修正されてとても嬉しいです...
どのように画像をロードしていますか?
使用している場合:
[UIImage imageNamed:(NSString *)]
次に、正当な理由があることを確認する必要があります。キャッシュが必要な画像を頻繁に使用している場合は、これが適切なオプションです。それ以外の場合は、使用することをお勧めします
[UIIMage imageWithContentsOfFile:(NSString *)]
iOS では、imageNamed を介してロードされたイメージのリリースに問題があるようです。たとえそれへの参照がなくなったとしてもです。アプリには画像への参照がなくなるため、おそらくメモリの警告は表示されません。しかし、それはメモリが解放されたという意味ではありません。iOS は、これらのイメージを必要以上に長くメモリに保持する傾向があります。通常はメモリ警告が表示される場合は、アプリを終了するだけです。
また、自動参照カウント (ARC) を有効にすることを強くお勧めします。
Edit -> Refactor -> Convert to Objective-C ARC...
私はかなり長い間同様の問題を抱えていました。アプリに上記の変更を加えると、クラッシュが停止し、imageNamed を介して同じ画像を何度もリロードしたときに発生するメモリ リークが停止しました。
それが役立つことを願っています。
メガディトス。これはとても安心しました。私も画像を多用するアプリ (アニメーション用にたくさんのUIImage
オブジェクトUIImageView
) を実行しており、アプリのデリゲートに通常のメモリ警告コードがありますが、トリガーされることはありません。それにもかかわらず、画像を読み込んで描画し、画像ビューにコミットすると、Instruments は問題を示しました。私は ARC を使用してCGImageRef
おり、s などからのリークを取り除きましたが、1 日の終わりに ImageView に十分な数の画像をロードすると、すぐにログに警告が表示されずにクラッシュが発生します。メソッドのコールバック、またはインストゥルメント。このアプリは、「あなたの許可なく」ラグをその下から引き出すだけです。
まだiPad2でこれを試す機会はありませんでしたが、とにかく、少なくとも最小限のコンソールメッセージか何か、いくつかの兆候が必要です. 読み込みのほとんどは、メイン スレッドではなく、自分の GCD キューで行われますが、定義上、画面コントロールの更新はメイン スレッドで行う必要があります。したがって、実行がプルされようとしているときにそれがブロックされている場合、匿名の失敗が発生する可能性があると思います。ただし、これがある種のコンソールメッセージを取得するのに役立つことは確かです。