Comparer<object>.Default.Compareメソッドでbyte配列の比較ができない

以下のコードが.NETでは動作するがXamarin.iOSでは動作しない。

byte[] v1 = new byte[]{0,1};

int ret = Comparer<object>.Default.Compare(v1, v1);  // <-ここで例外。メッセージは"does not implement right interface"

バグ?仕様?

追記 2014/02/12 10:21

サンプルコードが間違っていたので修正

追記 2014/02/13 10:17

最新のMono Frameworkのソースコードは修正されていて例外にはならない模様。しかしながらこの修正がいつXamarin.iOSに取り込まれるのかは不明。

DispatchQueue内でNSRunLoop.Current.RunUntilを呼び出すコードが動かなくなってしまった その2

前回の記事の動かなくなったコードをObjective-Cで記述してみた。Xcodeはv5.0.2を使用。

bool alertFinished;

- (void)hoge1:(id)sender {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    dispatch_sync(dispatch_get_main_queue(), ^{
      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Title"
      message:@"Test"
      delegate:self
      cancelButtonTitle:@"Cancel"
      otherButtonTitles:@"OK", nil];

      [alert show];

      alertFinished = NO;
      while (alertFinished == NO) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]];
      }
    });
  });
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
  alertFinished = YES;
}

結果、嫌な予感はしていたがXamarin.iOSと同じ症状に。
仕様が変わったんだろうか。原因は分かったけど対処方法が全く不明。困った。


追記 2014/02/10 18:19
ひょっとしてここに答えがあるでは。

DispatchQueue内でNSRunLoop.Current.RunUntilを呼び出すコードが動かなくなってしまった

元々は本家Objective-Cでも使われているテクニックで、非同期処理を同期処理にするためにNSRunLoop.Current.RunUntilを使うテクニックがある。
これをDispatchQueueからDispatchAsync経由で呼び出している箇所があるのだが、

DispatchQueue.GetGlobalQueue(DispatchQueuePriority.Low).DispatchAsync(delegate {
  DispatchQueue.MainQueue.DispatchAsync(delegate () {
    UIAlertView alert = new UIAlertView();

    bool dismiss = false;
    alert.Message = "TEST";
    alert.AddButton("OK");
    alert.Dismissed += delegate {
      dismiss = true;
    };
    alert.Show();
    while (!dismiss) {
      NSRunLoop.Current.RunUntil(NSDate.FromTimeIntervalSinceNow(0.5));
    }
  });
});

※本当はもっと複雑なのだが省略するとこのようなコードになる。

最近、このコードがおかしな挙動を起こすようになってしまった。
具体的にはUIAlertViewは表示されるもののタップ操作にまったく反応しない。なので画面上の操作が全体的に無視されているような状態になる。
デバッグでNSRunLoop.Current.RunUntilの所でBreakできるのでいちおうUIスレッドは固まったりしていない模様。

原因は不明。以前に比べるとXamarin.iOSXcodeもバージョンアップしているのでどっちに原因があるのかもよくわからない。
そもそも動作していたのが奇跡だったのだろうか。


追記 2014/02/12 12:32
解決出来た。そして"動かなくなった"のではなかった。"そもそも動いていなかった"の間違いだった。XcodeとXamarin.iOSのバージョンはまったく関係がなかった。

mtouchコマンドがインターネットアクセスを要求してくる

Xamarin.iOSのビルドをシェルスクリプトで定期的に行っているのだが、ある時から突然動作しなくなってしまった。
シェルスクリプトといっても単純でXamarin.iOSのmdtoolコマンドにソリューションを読み込ませてビルドさせているだけのもの。
状況としてはこのmdtoolのプロセスが標準エラーも標準出力もなにも出力しなくなり、かといってゾンビになっているわけでもないよくわからない状態で停止してしまうというもの。
調べてみるとmdtoolがWEBアクセスをしていて、しかもこの接続が何度も再試行されていた。

mdtool    32999 xxxxx   13u  IPv4 0x0000000000000000      0t0  TCP xxxxx.xxx.xxx:52983->store.xamarin.com:https (SYN_SENT)

使用していたXamarin.iOSがv7.0.5.2(Beta)だったのでリリース版v7.0.5.2に入れ直してみたところ止まることはなくなったものの、ビルド自体が非常に遅くなってしまった。相変わらずWEBアクセスはしている(今度はmtouchコマンド)ので数回の再試行で諦めてくれるもののタイムアウトするまで処理が止まってしまうのだろうか。

該当PCはインターネットアクセスするためにプロキシが必要だが、業務上インターネットアクセスは禁止されているのでプロキシは無効になっている。とりあえずプロキシを有効にすることで問題が回避できた。

以前からこうだったのかは不明。
インターネットアクセスできなければビルドできないなんて作りが悪すぎると思うのだが。

VisualStudioであるフォルダ以下の全てのソースコードをリンクとしてプロジェクトに取り込みたいとき

VisualStudioでソースコードを共有する場合、ソースコードをプロジェクトにリンクする機能がある。
しかしあるフォルダ以下を全てリンクしたい時、フォルダ階層やソースコードの数が変化するとその度にプロジェクトを変更しなければならない。これはかなり面倒くさい。

この場合csprojファイルを直接編集することで、特定のフォルダ以下の全てのソースコードを再帰的にリンクとしてプロジェクトに取り込ませることができる。
csprojファイルをエディタなどで開いて、以下の行を追加する。

  <ItemGroup>
    <Content Include="..\..\src\TestClass\**\*.*">
      <Link>TestClass\%(RecursiveDir)%(FileName)%(Extension)</Link>
    </Content>
  </ItemGroup>

この場合はcsprojファイルを起点にして2つ上のフォルダのsrc\TestClassフォルダ以下の全てのファイルをリンクとして取り込める。

  <ItemGroup>
    <Content Include="..\..\src\TestClass\**\*.cs">
      <Link>TestClass\%(RecursiveDir)%(FileName)%(Extension)</Link>
    </Content>
  </ItemGroup>

としてcsファイルだけをリンクしたり、

  <ItemGroup>
    <Content Include="..\..\src\TestClass\**\*.*" Exclude="..\..\src\TestClass\**\Test.cs">
      <Link>TestClass\%(RecursiveDir)%(FileName)%(Extension)</Link>
    </Content>
  </ItemGroup>

としてTest.csだけ省いたりすることができる。

Xamarin.iOS製Appのプロファイリングについての備忘録

  • "メモリ"と"パフォーマンス(関数呼び出し)"の2種類を取得できる
  • "パフォーマンス"はシミュレータ上で実行した時のみ取得可能
  • プロファイル結果ファイルを圧縮する設定にすると解析が不可能(実際は不可能では無いが止まったかのように遅い)
  • "パフォーマンス"の解析結果をGUIで見る方法が不明("メモリ"はHeapShotアプリで可能)
  • 解析ツールの場所は/Library/Frameworks/Mono.framework/Versions/Current/bin/mprof-report

"パフォーマンス"の解析の方法

$ /Library/Frameworks/Mono.framework/Versions/Current/bin/mprof-report --method-sort=total --traces --out=result.txt --verbose --maxframes=0 profiler-output.mlpd

呼出元関数を出力したい場合は--maxframes引数の数値を大きくする

_WebTryThreadLock(bool) 例外の原因

Xamarin.iOSで開発中のアプリで、特定の操作で100%クラッシュするという報告をもらった。
操作としてはUIButtonなボタンとUITapGestureRecognizerを追加したUIViewと仮想キーボード上のキーの3カ所を同時に数回タップするというもの。発見した人凄い。

エラーの詳細

エラー発生時に得られる情報は以下となる。エラー毎にログの内容は多少異なるが、特長としては必ずWebThreadという名前のスレッド内で例外が起きる。

コンソールログ
bool _WebTryThreadLock(bool), 0x3ca430: Multiple locks on web thread not allowed! Please file a bug. Crashing now...
以下略
デバイスログ
(略)
Exception Type:  EXC_BAD_ACCESS (SIGABRT)
Exception Codes: KERN_INVALID_ADDRESS at 0xbbadbeef
Crashed Thread:  2

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	0x30b0d004 0x30b0c000 + 4100
1   libsystem_kernel.dylib        	0x30b0d1fa 0x30b0c000 + 4602
2   CoreFoundation                	0x37e903ec 0x37e03000 + 578540
3   CoreFoundation                	0x37e8f0ea 0x37e03000 + 573674
4   CoreFoundation                	0x37e1249e 0x37e03000 + 62622
5   CoreFoundation                	0x37e12366 0x37e03000 + 62310
6   GraphicsServices              	0x362f5432 0x362f1000 + 17458
7   UIKit                         	0x30cc3cce 0x30c92000 + 203982
8   MYAPP                         	0x00189228 0x1000 + 1606184
9   MYAPP                         	0x0013d158 0x1000 + 1294680
10  MYAPP                         	0x0007e7c8 0x1000 + 513992
11  MYAPP                         	0x0073c6c4 0x1000 + 7583428
12  MYAPP                         	0x00f14168 0x1000 + 15806824
13  MYAPP                         	0x00f83494 0x1000 + 16262292
14  MYAPP                         	0x00f861b4 0x1000 + 16273844
15  MYAPP                         	0x00f86408 0x1000 + 16274440
16  MYAPP                         	0x00f171d0 0x1000 + 15819216
17  MYAPP                         	0x00f119be 0x1000 + 15796670
18  MYAPP                         	0x000021b0 0x1000 + 4528

Thread 1 name:  Dispatch queue: com.apple.libdispatch-manager
Thread 1:
0   libsystem_kernel.dylib        	0x30b0d3a8 0x30b0c000 + 5032
1   libdispatch.dylib             	0x347e7f04 0x347e4000 + 16132
2   libdispatch.dylib             	0x347e7c22 0x347e4000 + 15394

Thread 2 name:  WebThread
Thread 2 Crashed:
0   libsystem_kernel.dylib        	0x30b1d32c 0x30b0c000 + 70444
1   libsystem_c.dylib             	0x330ae208 0x33061000 + 315912
2   libsystem_c.dylib             	0x330a7298 0x33061000 + 287384
3   MYAPP                         	0x00f219aa 0x1000 + 15862186
4   MYAPP                         	0x00f12bb4 0x1000 + 15801268
5   libsystem_c.dylib             	0x330b87e6 0x33061000 + 358374
6   JavaScriptCore                	0x363e6fe8 0x36315000 + 860136
7   WebCore                       	0x311f77ec 0x311f1000 + 26604
8   CoreFoundation                	0x37e90b14 0x37e03000 + 580372
9   CoreFoundation                	0x37e8ed50 0x37e03000 + 572752
10  CoreFoundation                	0x37e8f02a 0x37e03000 + 573482
11  CoreFoundation                	0x37e1249e 0x37e03000 + 62622
12  CoreFoundation                	0x37e12366 0x37e03000 + 62310
13  WebCore                       	0x3129ac9c 0x311f1000 + 695452
14  libsystem_c.dylib             	0x3306f72e 0x33061000 + 59182
15  libsystem_c.dylib             	0x3306f5e8 0x33061000 + 58856

(以下略)

原因究明

WebTryThreadLockキーワード

WebTryThreadLockというキーワードから検索出来る情報は「UIスレッド以外からのUIを操作した」というのが一番多い。しかしこの場合コンソールログに

Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...

と表示されるので今回のエラーとは関係がない模様。そしてなによりこのアプリはマルチスレッドを使っていない。

WebThreadキーワード

iOSでは自動的に作られるスレッドなんだろうか?自分で制御しているスレッドではないのでどうにもならない。またこのキーワードからは何も得られなかった。

XCodeデバッグ

Xamarin Studioからデバッグした場合、例外の発生と共にデバッグが終わってしまう。なのでXCodeデバッグしてみた。
例外発生付近のアセンブラのコードをみているとスレッドロックが失敗して0x000000000なアドレスに0xbbadbeefというデータを書き込んでいた(Assert的処理だろうか)。不正なアドレスに書き込みしているのでEXC_BAD_ACCESS。0xbbadbeefはBad Beef(悪い牛肉)という意味だろうか。
つまりデバイスログにあるEXC_BAD_ACCESSは2次的なものであって、直接の例外理由ではないことが判った。
ではどうしてスレッドロックがおかしくなったのか?
さすがにこれ以上アセンブラレベルで解析するのは辛いので諦めた。

コードを精査

手間なので後回しにしていた「書いてあるコードを一行ずつコメントアウトして逐一動作をチェック」をおこなってようやくエラーがでなくなった。
場所はUIButtonのTouchUpInsideイベントを使っている箇所だった。



結局具体的な原因は分からないままである。
対処療法的になるがコード中のResponderChain系イベントを使っている箇所を、全てUIGestureRecognizerに置き換えることでエラーがでなくなった。