読者です 読者をやめる 読者になる 読者になる

24/7 twenty-four seven

iOS/OS X application programing topics.

一時オブジェクトを大量に使う(メモリを消費する)処理をループする場合は、ループの中で自動解放プールを生成する

Cocoa Objective-C

LDR touchではオフラインでも読めるようにフィードをキャッシュする仕組みがあります。
キャッシュするところのコードはだいたい以下のような感じになっています。
フィードのリストから、IDを取り出し、対応するエントリーのデータをダウンロードします。

- (void)saveEachEntries {
	LDRTouchAppDelegate *sharedLDRTouchApp = [LDRTouchAppDelegate sharedLDRTouchApp];
	LDRManager *loginManager = sharedLDRTouchApp.loginManager;
	NSDictionary *feed;
	for (feed in feedList) {
		if ([self isCancelled]) {
			break;
		}
		NSString *subscribe_id = [NSString stringWithFormat:@"%@", [feed objectForKey:@"subscribe_id"]];
		NSDictionary *entries = [loginManager loadEntries:subscribe_id];
		[self saveEntries:entries];
	}
}

このコードには問題があり、エントリーデータを格納するオブジェクトが、ループを終了しない限り解放されないため、未読のフィードが大量にある場合は、メモリが足りなくなる恐れがあります。
自動解放プール(NSAutoReleasePool)はイベントループが一回りするたびにオブジェクトを解放します。
ループ中はイベントループに戻らないので、この間に作られた一時オブジェクトは解放されません。
この部分です。

NSDictionary *entries = [loginManager loadEntries:subscribe_id];

上のようにallocを使わずに生成されるオブジェクトは、autoreleaseされているので、releaseを送ってしまうと後で二重解放となりクラッシュしてしまいます。
このような場合は、ループの中で自動解放プールを作り、ループが一周するごとにオブジェクトを解放するようにします。

for (feed in feedList) {
	if ([self isCancelled]) {
		break;
	}
		
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		
	NSString *subscribe_id = [NSString stringWithFormat:@"%@", [feed objectForKey:@"subscribe_id"]];
	NSDictionary *entries = [loginManager loadEntries:subscribe_id];
	[self saveEntries:entries];
		
	[pool release];
}

現在の時点で公開中のLDR touchは自動解放プールを作っていないので、未読フィードが多い場合メモリが足りずにクラッシュすることがあります。
上記の修正と、レートやフォルダ別表示に対応した新バージョンを、先ほど提出しました。
普通に審査を通過すれば、7〜9日でリリースされる予定です。