ふたつの日時の差を求めたい(年月日)

// ふたつの日時の差を求める(年月日のみ)
NSCalendar *calendar = [NSCalendar currentCalendar];

NSDateComponents *comps1 = [[NSDateComponents alloc] init];
[comps1 setYear:2009];
[comps1 setMonth:3];
[comps1 setDay:10];
NSDate *date1 = [calendar dateFromComponents:comps1];

NSDateComponents *comps2 = [[NSDateComponents alloc] init];
[comps2 setYear:2010];
[comps2 setMonth:5];
[comps2 setDay:20];
NSDate *date2 = [calendar dateFromComponents:comps2];

NSDateComponents *diff = [calendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:date1 toDate:date2 options:0];

NSLog(@"Year: %d Month: %d Day: %d", [diff year], [diff month], [diff day]); // => Year: 1 Month: 2 Day: 10 

月日、時分秒を指定して日時オブジェクトを生成したい

// 年月日、時分秒を指定して日時オブジェクトを生成する
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:2010];
[comps setMonth:1];
[comps setDay:2];
[comps setHour:3];
[comps setMinute:4];
[comps setSecond:5];
NSDate *date = [calendar dateFromComponents:comps];
NSLog(@"date: %@", date); // => date: 2010-01-02 03:04:05 +0900

日時オブジェクトから年月日や時分秒、曜日をとりだしたい

NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps;

// 年月日をとりだす
comps = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) 
					fromDate:date];
NSInteger year = [comps year];
NSInteger month = [comps month];
NSInteger day = [comps day];
NSLog(@"year: %d month: %d, day: %d", year, month, day);
//=> year: 2010 month: 5, day: 22

// 時分秒をとりだす
comps = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit)
					fromDate:date];
NSInteger hour = [comps hour];
NSInteger minute = [comps minute];
NSInteger second = [comps second];
NSLog(@"hour: %d minute: %d second: %d", hour, minute, second);
//=> hour: 18 minute: 24 second: 31

// 週や曜日などをとりだす
comps = [calendar components:(NSWeekCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit)
					fromDate:date];
NSInteger week = [comps week]; // 今年に入って何週目か
NSInteger weekday = [comps weekday]; // 曜日
NSInteger weekdayOrdinal = [comps weekdayOrdinal]; // 今月の第何曜日か
NSLog(@"week: %d weekday: %d weekday ordinal: %d", week, weekday, weekdayOrdinal);
//=> week: 21 weekday: 7(日曜日) weekday ordinal: 4(第4日曜日)

オートリリースプールとスレッドとNSOperation

Cocoaのリファレンスカウント式のメモリ管理で利用されるオートリリースプールはメインスレッドに対しては自動的に作られて自動的に解放されるので普段はあまり意識することはないのだけど、performSelectorInBackground:withObject:などを利用して別スレッドで処理を行う時には自前でオートリリースプールを作成しないとメモリリークが発生してしまいます。

以下は自前オートリリースプール作成のサンプルコード。doItメソッドは別スレッドで処理が行われるため、メソッド内部でオートリリースプールを自前で立てています。

- (void)doIt {
	// オートリリースプールを作成
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSString *s = [[[NSString alloc] initWithFormat:@"hoge"] autorelease];
	NSLog(@"%@", s);
	// オートリリースプールとプールにたまったオブジェクトをリリース
	[pool release];
}

- (IBAction)doButtonDidPress {
	[self performSelectorInBackground:@selector(doIt) withObject:nil];
}

doItメソッドの中のNSAutoreleaseを生成する部分を取り除くと下記のようにコンソールに「オートリリースプールがないよー」と警告メッセージが表示されるので実際にオートリリースプールが効いているのが確認できる。

*** __NSAutoreleaseNoPool(): Object 0x5b3a440 of class NSCFString autoreleased with no pool in place - just leaking

(おまけ)オートリリースプールとNSOperation

別スレッドを立ててバックグラウンドで処理を行うもうひとつの方法としてNSOperationを利用する方法もある。NSOperationを利用する時には、なぜだか分からないのだけど、オートリリースブールを立てる必要なないみたい。NSOperationQueueかNSOperationの親クラスのあたりでヨロシクやってくれてるのかもねー。

- (void)doIt {
	// オートリリースプールがなくても警告メッセージが出ない
	NSString *s = [[[NSString alloc] initWithFormat:@"hoge"] autorelease];
	NSLog(@"%@", s);
}

- (IBAction)doButtonDidPress {
	NSOperationQueue *q = [[NSOperationQueue alloc] init];
	NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self 
														   selector:@selector(doIt) 
															 object:nil];
	[q addOperation:op];
	[op release];
	[q release];
}

メモリリークを調べたい

iPhoneアプリケーション開発ではガベージコレクションが使えないためJavaRubyのようなガーベージコレクションを備えている言語環境と異なりメモリリークの危険と常に隣り合わせです。しかし、iOS SDKにはメモリリークの検出をサポートするためのツールが用意されています。これを利用しない手はありませんね。

iOS SDKが提供するメモリリーク検出をサポートするツールは以下の2つがあります。

  • 静的コード解析を行う「静的アナライザ」
  • 動的プログラム解析を行う「Instruments」

静的アナライザはソースコードを静的に解析してメモリリークしていそうな箇所を推測する「静的コード解析」を、Instrumentsは実際にアプリを動かしながらメモリリークを検出する「動的プログラム解析」をそれぞれ行います。

静的アナライザ

静的アナライザを利用するには、Xcodeのメニューから「ビルド - Build and Analyze」を選択してプロジェクトをビルドするだけです。「Build and Analyze」を選択してアプリケーションをビルドすると、アプリケーションのビルドにつづいて静的アナライザによるソースコード解析が行われ、メモリリークしていそうなところをビルド結果へ出力してくれます。

例えば、以下のコードを静的アナライザに解析させてみると、

NSInteger n = (rand() % 7) + 1; // 1から7の間でランダムに取得
// messageをリリースしていないためメモリリークが発生する
NSString *message = [[NSString alloc] initWithFormat:@"%d人の侍", n];
NSLog(@"%@", message);

以下のような解析結果が得られます。
解析結果から変数messageがメモリリークしそうなことが分かります。

メニューから「Build - Analyze」を毎回選択するのが面倒な場合は、プロジェクトの設定内の「ビルドオプション - 静的アナライザを実行」にチェックを入れるといいでしょう。Xcodeのメニューから「ビルド - ビルド」を選択したときにも静的アナライザが実行されるようになります。


iOS SDK 4.1での静的アナライザの不具合

これを執筆している状況でのiOS SDKの最新バージョンであるiOS SDK 4.1では、アクティブSDKに「Simulator」を選択していると静的アナライザが正しく実行されない不具合があります。

アクティブSDKを「Simulator」ではなく「Device」を選択すると静的アナライザは正しく動作するため、この不具合が直るまでは静的アナライザをかけるときには「Device」を選択するようにしてください。

Instruments

次はInstrumentsを使ったメモリリーク検出です。動的プログラム解析を行うInstrumentsは静的アナライザでは見つけられないメモリリークを発見するのに役立ちます。

Instrumentsを使ったメモリリーク検出を利用するには、Xcodeのメニューから「実行 - パフォーマンスツールを使って実行 - Leaks」を選択すると、計測対象のiPhoneアプリとInstrumentsが起動します。

起動されたiPhoneアプリの操作中にメモリリークが発生すると、Instrumentsがそのメモリリークを検出しDetaileビューの「Leaks - Leadked Blocks」へとリークしたオブジェクトに関する情報を出力します。またExtended Detailでそのメモリリークしたオブジェクトがソースコード中のどこで生成されたかを確認することができます。