2013年01月09日

NSDateFormatterの生成時間

アップルの技術文書や解説ビデオを見ると必ずと言っていいほど、NSDateFormatterは生成コストがすごいからできるかぎり使い回せという趣旨の発言をしている。

本当かどうか調べてみた。単純にNSDateFormatterを生成してフォーマットを設定しNSDateを文字列に変換する。

case 1)は事前にNSDateFormatterを生成しフォーマットもセットしておいた後、stringFromDate:を実行するだけで1000回繰り返す。
case 2)は事前にNSDateFormatterを生成するのは同じだが、毎回フォーマットをセットする。
case 3)は毎回NSDateFormatterを生成する。

破棄時間は対象外にしたいので、ARCはOFFに設定。NSDateFormatterはautoreleaseメッセージを送っておく。

その結果が以下:
case 1)time = 10 235 446(nsec)
case 2)time = 11 717 976(nsec)
case 3)time = 152 146 810(nsec)

「生成」「フォッマット設定」「文字列変換」の一連の流れのうちNSDateFormatter生成時間が9割以上を占めることがわかる。

なるほどNSDateFormatterの生成が時間を要する処理だと言うことがわかった。

私が作っているアプリケーションはNSDateFormatterを頻繁にアプリの色々なところで使っているので考慮する必要がある。だがループ内で何度も生成するとか、実時間処理が気になるゲームアプリなどでなければわざわざ生成したNSDateFormatterをインスタンス変数などで保持するまでのことは必要ない程度のオーバーヘッドだとは思う。

なお、iPhone実機でやったほうがいいのだろうけど、そんなに大きな違いがあるとは思えないのでMacで実行した。

MacBook Pro 2.6GHz Intel Core i7
16GB 1600 MHz DDR3
499.42GB SSD
Mac OS X 10.8.2



posted by 永遠製作所 at 17:15| 東京 ☁| Comment(0) | TrackBack(0) | Cocoa | このブログの読者になる | 更新情報をチェックする

2012年11月25日

[Xcode]メソッド名のリストを表示してジャンプする

Xcodeのエディタで、クラスやメソッドに直接移動したい場合に便利なのが「ドキュメントアイテムをメニューに表示する機能(Show Document Items)」だ。

エディタ上部にプロジェクト名からの階層で表示されている。通常「プロジェクト名>グループ名>ファイル名>メソッド名」のような階層表示されており、名前をクリックするとメニューがポップアップされてその階層にある別のファイル名やメソッド名をリストしている。メニューで選ぶとそこにジャンプするというものだ。

DocMenu.png

DocMenu1.png

各階層には、Show Group Files/Show Top Level Itemsなどの名前がついているがこれはあまり気にしなくていいだろう。

DocMenu4.png

DocuMenu5.png

ちなみに、それぞれにキーボードショートカットが割り当てられている。


Show Document Items ^6
Show Group Files ^5
Show Top Level Items ^4


ドキュメントアイテムは昔は登場順のほかに、アルファベット順にできたのだが今は出来ないみたいで方法が見つからない。時々できたらいいのになあと思う。

その代わりにいくつか目的の場所を探しやすい機能がついている。

メソッド名意外にもC関数やプロパティ、インタフェース宣言、インプリメンテーション宣言、デファインマクロなどもリストアップしてくれる。

その他よく使うのがマーク。

#pragma mark Actions

など「#pragma mark」のあとに書いた任意の文字列をメニューに表示するので、グループ毎のまとまりを作る場合に使える(グループ毎のまとまりを明示するのにカテゴリを使ってもいいがこっちのほうが簡単)。

特別なマークとして「-(ハイフン)」一個だけ書くとメニューの区切り線を置いてくれるので、名前を付けるよりもわかりやすい場合がある。

#pragma mark -



DocMenu2.png

その他特別なコメントを付けることができる。

// TODO: Do Something
// FIXME: Crash here


DocMenu3.png

1行コメントではなく、C言語のブロックコメント内でもよい。上記キーワードを書けばその行を認識してくれる。「TODO」は将来実装しようと思っているが未実装の項目が書ける。「FIXME」はバグの場所を示して後で修正できるように。いずれも検索しなくてもメニューで表示されるのでわかりやすい。

DocMenu6.png

他にもキーワードがあるのかもしれないが今のところこの二つしか見つけていない。

DocMenu7.png

DocMenu8.png
ラベル:Xcode 初心者向
posted by 永遠製作所 at 20:47| 東京 ☀| Comment(0) | TrackBack(0) | Cocoa | このブログの読者になる | 更新情報をチェックする

2012年11月09日

[NSString]文字列を連結した文字列を作る



ちょっと前だが、フェンリルの開発者ブログでこんな記事があった。

文字列を Objective-C っぽくクールに作る

ここに書かれている方法はその通りでこういう実装は見習いたいものだと思う。
特にNSArrayを使って文字列の配列を作成してから[NSArray componentsJoinedByString:] を使って連結するやり方は、先頭または末尾を特別扱いする必要がないのであらかじめいくつデータがあるのかわからない場合などにとても有用だ。

が、ふと別の観点から見たらどうなんだろう?と考えてみた。速度面である。

それぞれのやり方で実行時間を計測したらこうなった。

2.6GHz Intel Core i7
MacOS X 10.8.2



#import
#import

#define TEST 0

int main(int argc, const char * argv[])
{

@autoreleasepool {
NSString *aString;
uint64_t start, elapsed;
mach_timebase_info_data_t base;
mach_timebase_info(&base);

start = mach_absolute_time();
for ( int i = 0; i < 100; i++ ) {
aString = @"";
#if TEST == 0
NSMutableString *muString = [NSMutableString string];
for ( int j = 0; j < 100; j++ ) {
[muString appendFormat:@"Time Test (%d)\n",j];
}
// time = 4343708(nsec)
#elif TEST == 1
for ( int j = 0; j < 100; j++ ) {
aString = [aString stringByAppendingFormat:@"Time Test (%d)\n",j];
}
// time = 25425541(nsec)
#elif TEST == 2
for ( int j = 0; j < 100; j++ ) {
aString = [NSString stringWithFormat:@"%@\nTime Test (%d)",aString,j];
}
// time = 19513834(nsec)
#elif TEST == 3
NSMutableArray *anArray = [NSMutableArray array];
for ( int j = 0; j < 100; j++ ) {
[anArray addObject:[NSString stringWithFormat:@"Time Test (%d)",j]];
}
aString = [anArray componentsJoinedByString:@"\n"];
// time = 13281454(nsec)
#elif TEST == 4
NSMutableArray *anArray = [NSMutableArray arrayWithCapacity:100];
for ( int j = 0; j < 100; j++ ) {
[anArray addObject:[NSString stringWithFormat:@"Time Test (%d)",j]];
}
aString = [anArray componentsJoinedByString:@"\n"];
// time = 12225972(nsec)
#endif
}
elapsed = mach_absolute_time() - start;
uint64_t nsec = elapsed * base.numer / base.denom;
printf("time = %lld(nsec)\n",nsec);
}
return 0;
}


100行の文字列を作成するのを100回繰り返す場合。実行時間だけで言うと、
[NSMutableString appendFormat] = 4343708(nsec)
[NSArray componentsJoinedByString:] = 13281454(nsec)
[NSString stringWithFormat:] = 25425541(nsec)
[aString stringByAppendingFormat:@"Time Test (%d)\n",j] = 19513834(nsec)

の順になる。

意外なことに[NSMutableString appendFormat]を使うのが一番いいという結果になった。メモリ管理の効率がいいせいなのかもしれない。

でもよっぽど実行時間を節約しなければならない状況でなければ[NSArray componentsJoinedByString:] を使うのがいいという考えは変わらない。
ラベル:NSArray NSString
posted by 永遠製作所 at 18:00| 東京 ☀| Comment(0) | TrackBack(0) | Cocoa | このブログの読者になる | 更新情報をチェックする

2012年11月08日

[NSString] 空文字列の判定


NSStringを返すメソッドがあって結果が空文字列の場合の判定をしたいという場面はよくある。
この時、戻り値を @"" と比較して空文字列かどうか判定するのは適切とは言えない。


if ( aString == @"" ) {
NSLog(@"Same!");
} else {
NSLog(@"Different!");
}


これはC言語でも同じだが、aStringはNSStringオブジェクトへのポインタなのだからこの比較はオブジェクトが同じアドレスにあるかどうかでしかない。

そして上記のように@""と書くとこれは文字列定数として静的に確保されたメモリ上のオブジェクトのことである。だから動的に作成した空文字列は通常は別のメモリアドレスに配置される。

下記がその例。

int main(int argc, const char * argv[])
{
@autoreleasepool {
NSString *aString;
aString = @"";
NSLog(@"[%@] %p",aString,aString);
if ( aString == @"" ) {
NSLog(@"Same!");
} else {
NSLog(@"Different!");
}
aString = [NSString stringWithFormat:@"%@",@""];
NSLog(@"[%@] %p",aString,aString);
if ( aString == @"" ) {
NSLog(@"Same!");
} else {
NSLog(@"Different!");
}
}
return 0;
}


実行結果


2012-11-08 13:28:18.699 CodeTest[63648:303] [] 0x1000010d0
2012-11-08 13:28:18.701 CodeTest[63648:303] Same!
2012-11-08 13:28:18.701 CodeTest[63648:303] [] 0x7fff7e6b5f90
2012-11-08 13:28:18.701 CodeTest[63648:303] Different!


空文字列かどうかは通常下記にて比較する。


if ( [aString compare:@""] == NSOrderedSame ) {
NSLog(@"Same!");
} else {
NSLog(@"Different!");
}
if ( [aString length] == 0 ) {
NSLog(@"Same!");
} else {
NSLog(@"Different!");
}


後者の方法のほうがより優れている。結果が正常な文字列にならなければ空文字列ではなく nil を返すような仕様であっても、lengthが0を返すから(nilへのメッセージ送信の結果はnilまたは0になる)。


この方法はNSArrayの場合にも応用できる。結果を配列で返すメソッドがあったとして、適合するデータがひとつもなかったらどうすればいいだろうか?nilを返す?要素数が0個のオブジェクトを返す?nilのほうがわかりやすいと思うのだが、そうすると呼び出された側で結果の個数を判定してnilを返すかどうか処理を振り分ける必要がある。呼び出した側でも結果がnilかどうかで分岐するのであれば無駄である。

結果の判定は要素数が0個かどうかでだけ判断する。

if ( [anArray count] == 0 ) {
NSLog(@"no item");
} else {
NSLog(@"has items");
}


これはanArray が nilであっても期待通りに動作する。

呼び出された側では以下のように書き、結果が0個でもそのまま返すように書けばよいが、nilを返すようにしてもそのまま動作する。

NSMutableArray *result = [NSMutableArray array];
for ( ... ) {
...
}
return result;
posted by 永遠製作所 at 15:05| 東京 ☀| Comment(0) | TrackBack(0) | Cocoa | このブログの読者になる | 更新情報をチェックする

2012年08月19日

NSURLの分割

URL(URI)を各要素に分割して処理する場合に、NSURLのリファレンスの説明が不十分なので要約。というかリファレンス読む人は全員RFC1808熟知しているよねって前提なんですかね?

NSURLの各要素を取り出すメソッドには以下のようなものがある。

– absoluteString
– absoluteURL
– baseURL
– fragment
– host
– lastPathComponent
– parameterString
– password
– path
– pathComponents
– pathExtension
– port
– query
– relativePath
– relativeString
– resourceSpecifier
– scheme
– standardizedURL
– user


このうちURLの表現を変えたもの(standardizedURL,absoluteURL)、パスの表現を変えたもの(absoluteString,relativeString,relativePath)は今回は省略。
また baseURL は URLWithString:relativeToURL: などで作った時に指定したbaseURLで相対パスも省略。あくまでも分解して各要素を取り出して使うための調査。

ただし、xxxString,とxxxPathの違いはString>PathでPathはスキーマやホスト部を除いた部分にあたるのでPathは使うことがあるかもしれない。

上記のメソッドのそれぞれで下記の各要素が取り出せる。


<scheme>://<user>:<password>@<host>:<port>/<path>;<parameterString>?<query>#<fragment>


なお path については 先頭の"/"を含む形になる。resourceSpecifier は "≶scheme>:"を除いた部分のすべて。

pathComponentsはpathを"/"で区切った各要素毎(NSStringの同名のメソッドと同等)、lastPathComponentは最後のパス要素(NSStringの同名のメソッドと同等)、pathExtensionはlastPathComponentの拡張子部分(NSStringの同名のメソッドと同等)。

となる、例をあげると以下のようになる。

"http://www.apple.com/"

scheme = http
host = www.apple.com


"http://www.apple.com/jp/osx/"

scheme = http
host = www.apple.com
path = /jp/osx/
lastPathComponent = osx


"https://guest:anonymous@localhost:8080/tmp/folder"

scheme = https
host = localhost
path = /tmp/folder
lastPathComponent = folder
user = guest
password = anonymous
port = 8080


"http://server1/cgi-bin/getlist.php;product?id=123456&sort=1#tag1"

scheme = http
host = server1
path = /cgi-bin/getlist.php
lastPathComponent = getlist.php
pathExtension = php
parameterString = product
query = id=123456&sort=1
fragment = tag1
resourceSpecifier = //server1/cgi-bin/getlist.php;product?id=123456&sort=1#tag1


注目はqueryが中身をそれ以上分解してくれないこと。&で区切って属性と値を=で結ぶという書き方は規格外とのこと。もしこういう形でURLを組み立てて値を取り出そうとしたら自分で実装しなければならない。perlとかphpではAPIが用意よされていたと思うんだがなぁ。

あとparameterStringは見たことがなかった。たまには規格に目を通すべきですね。

確認用のコードをgistに置いてみた。gistってこんな風に使うのであっているのかな?
ラベル:NSURL
posted by 永遠製作所 at 16:15| 東京 ☀| Comment(0) | TrackBack(0) | Cocoa | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。