ラベル Xcode 4.0.2 の投稿を表示しています。 すべての投稿を表示
ラベル Xcode 4.0.2 の投稿を表示しています。 すべての投稿を表示

2011年7月31日日曜日

Objective-C で文字列リテラルに \0 を含めたいときの作戦

Xcode 4.0 から LLVM が標準のコンパイラとなり、各種警告が非常に厳しくなっています。その中でも特に今回は文字列リテラルに \0 が含まれているときの警告について回避策を発見したのでご紹介したいと思います。

Objective-C では文字列リテラルは @"abesi" のように @"" で囲んで表現します。このリテラルは(あくまで推測で確定ではないのですが)コンパイラによってコンパイル時に CFSTR("abesi") に置換され、 CFStringRef 型としてプログラム中に定義されているようです。さて問題はここからで、 Xcode 4.0 が内部的に構文解析のために使っている LLVM がこのリテラル中に \0 、要するにNULL文字が含まれていると以下のような警告を出すようになってしまったのです
CFString literal contains NUL character
普通はNULL文字をリテラル中に含めたいということはまず無いのですが、SMTPの通信部分を書いたりしていると通信プロトコル自体が命令を \0 で区切れみたいな要件を求めてくるため、どうしてもリテラルの中に \0 を含めたいときが出てきます。 まぁ、具体的には以下のライブラリなんですけどね。

http://code.google.com/p/skpsmtpmessage/source/browse/trunk/SMTPSender/Classes/SKPSMTPMessage.m
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"\000%@\000%@", login, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
そして私と同じライブラリの同じ箇所でぶつかった人を発見。

http://stackoverflow.com/questions/5814520/how-do-i-create-a-login-string-that-contains-2-embedded-nulls-and-the-login-an

さて回避方法はないものかと探してみたところ、ちょうど良い回避策が発見できました。

http://stackoverflow.com/questions/6211908/nsstring-with-0

NSStringのstringWithFormat:に %C を埋め込んで、そこに 0 を渡せばうまくいくみたいです!
int main() {
NSString *string = [NSString stringWithFormat: @"Hello%CWorld!", 0];
NSData *bytes = [string dataUsingEncoding: NSUTF8StringEncoding];
NSLog(@"string: %@", string);
NSLog(@"bytes: %@", bytes);
return 0;
}
さっきの例だと、
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"%C%@%C%@", 0, login, 0, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
このようにすればばっちり上手く回避できました!

2011年5月29日日曜日

-ObjC とか -all_load って何をやってるのか調べてみた

よく外部のライブラリやFrameworkをiOSのプロジェクトに取り込むときに、つけないと動かないからつけてねと言われる、 Other Linker Flag = -ObjC -all_load のフラグ。これって何をやっているのか今までずっと気になっていたので、ちょっと調べてみました。

こういうことらしいです。
http://developer.apple.com/library/mac/#qa/qa1490/_index.html
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.
このフラグは、Objective-Cのクラスまたはカテゴリを定義しているライブラリに含まれる、全てのオブジェクトファイルをロードするようリンカに促します。これによってたいていの場合実行ファイルのサイズが大きくなってしまいますが(アプリに追加のオブジェクトコードが読み込まれるので)、既存のクラスに対するカテゴリを含むObjective-C静的ライブラリを正しく使えるようになります。
へぇぇ!全く知らなかったです。ちなみに -all_load はというと、
-all_load forces the linker to load all object files from every archive it sees, even those without Objective-C code. -force_load is available in Xcode 3.2 and later. It allows finer grain control of archive loading. Each -force_load option must be followed by a path to an archive, and every object file in that archive will be loaded.
-all_load は全てのオブジェクトファイルを全ての認識できる静的ライブラリ(アーカイブ)から全部ロードしてくる(意訳)
とかそういうことらしいです。なるほどねー。

ということは -all_load はより強力な -ObjC に見えますが、実際には -ObjC は -all_load と違って上記に記載されているオブジェクトファイルのロードの挙動変化以外にも何かやっているようで、実際 -all_load -ObjC 両方指定してあるとリンクできるのに、 -all_load だけではリンクが通らない場合がありました。なのでやっぱり必要なときはまず -ObjC だけを試してみて、それでも駄目なら両方設定するのがいいみたいですね。


余談ですが、上記の内容を調べていたとき、リンカとかローダについて詳しく学ぶには以下の本がよいとオススメしていただきました。

Linkers and Loaders (日本語訳版もありますが、訳がひどいので英語版の方がよいとのことです。ここでは日本語版のリンクはあえて紹介しません)
http://linker.iecc.com/

4789838072リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY)
坂井 弘亮
CQ出版 2010-08

by G-Tools


KindleかiBooksで欲しかったのですが、残念ながら紙の本しかないですね・・・><

2011年5月5日木曜日

atos を使ってアプリのクラッシュログを symbolicate する方法

iOSアプリがクラッシュするとクラッシュログがデバイスに残され、 Xcode のオーガナイザーからログを取得してバグの原因を解析できるのは皆さんご存知の通りだと思います。このとき、基本的には Xcode がクラッシュログがの中のシンボルを自動的に読める状態にしてくれる (symbolicate) のですが、どうも Xcode 4 になってからこの symbolicate がいまいちよく動いてくれないので、手動で symbolicate をする方法を調べてみました。

参考にしたページは以下の通り。
http://stackoverflow.com/questions/1460892/symbolicating-iphone-app-crash-reports


■atosの使い方

symbolicate をするには Xcode に付属している atos というコマンドラインツールと、ビルドの際に生成される dSYM と呼ばれるファイルを使います。

まずは dSYM ファイルを探します。 Xcode 4 の場合は、デフォルトで *~/Library/Developer/Xcode/DerivedData/プロジェクト名-ランダムな文字列/Build/Products/ビルド設定名/アプリ名.app.dSYM* に生成されるようになっているはずです。

dSYM ファイルが見つかったら、コンソールから atos を実行します。 atos の使い方は以下のとおりです。
atos -arch アプリがクラッシュしたデバイスのアーキテクチャ -o アプリ名.app.dSYM/Contents/Resources/DWARF/アプリ名 クラッシュした関数またはメソッドのアドレス
実際にやってみるとこんな感じです。
cd ~/Library/Developer/Xcode/DerivedData/MyProject-xxxxxxxxxxxxxxxxxxxxxxxxxxxx/Build/Products/Debug-iphoneos/
atos -arch armv7 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp 0x00072ce6
これで指定されたアドレスが symbolicate され、シンボル名および元のソースコード上の行数まで表示してくれます。超便利です。


■注意点

何回か試してみましたが、ときどき実際にクラッシュしたシンボルと違うシンボルが表示されることがあるみたいです。上記で紹介したやり方が間違っているのか、それともそういうものなのかわからないのですが・・・すんません>< しかしその場合でもだいたい表示されたシンボルの近くが問題の原因だったため、いまのところ参考ぐらいには使えています。

2011年5月3日火曜日

Xcode のビルド設定で $(inherited) を使って設定を継承する



AppleのXcode 2のころのドキュメントによるとXcodeのビルド設定は以下の順に設定内容が解決されていきます。
  1. ユーザーのデフォルトの環境変数設定(たいていは ~/.MacOSX/environment.plist で指定されている)
  2. Xcodeのビルトインデフォルト設定
  3. Xcodeのアプリケーション設定(これはXcode 4では廃止されている気がします)
  4. ベースとなるビルド設定ファイル(元のドキュメントには書いてありませんが、.xcconfigファイルを使ってビルド設定を指定可能です)
  5. プロジェクトのビルド設定
  6. ターゲットのビルド設定
  7. コマンドラインから起動した場合は、コマンドラインのビルド設定フラグの値
このとき、下位の設定は上位の設定を上書きして使用するようになっているのですが、ここで上位の設定をそのまま受け継いで使いたい場合には、設定項目に$(inherited)という特殊な値を指定することが可能です。

2011年4月17日日曜日

Xcode 4 で scheme が My Mac 64bit になって iPhone 向けのビルドが出来なくなった時の対処法



最近会社のプロジェクトで使う Xcode を Xcode 4 に乗り換えたのですが、全く新しく作り直された バグだらけでまともにビルドすら出来ない上に無料じゃなくなったどうしようもない出来映えの IDE ということで、何かとトラブルが多いようです。今回はその中でももっとも頻発したものをご紹介します。

たまに iOS 向けのプロジェクトを Xcode 4 で開いたときに、左上の scheme 選択欄に My Mac 64bit と表示されてしまって iPhone シミュレータや実機でビルドが出来なくなることがあるようです。

対処法はこちら。
http://stackoverflow.com/questions/5319251/xcode-4-the-selected-run-destination-is-not-valid-for-this-action

Build Settings を開き、 Base SDK の設定をいったん Mac OS X SDK に変更してから、もとの iOS SDK に戻すと問題が解決されるようです。これで一安心ですね。

Xcode 4.0 と Xcode 4.0.1 で再現することを確認しました。 Xcode 4.0.2 では未確認です。