1年弱業務にXamarin.Macを投入して様々なハマりどころを見つけたので,共有いたします。
主に投入しているのはインハウスツールで,自社クラウドサービスのクライアント開発にも利用しています。利用環境がほぼMacであるため,従来JavaやPythonが使われていた場所を徐々にXamarin.Macに置き換えています。
本当はUnified APIについて,Xamarin.Mac Mobile Frameworkについて掘り下げたかったけどそれはまた…
##はまるんじゃなく,はめる
Xamarin.Macが完璧にはまる=フィットする場面は残念ながらあまりありません。MacではXCodeを使って(ほとんど無料で)開発が可能ですし,デスクトップアプリでWin/Mac両対応となると大抵Javaが選択されます。
つまり業務にはめるべく,いろいろな工作が必要です。以下,私がXamarin.MacやMonoを導入するために・導入効果をアピールするためにこの一年やってきたことです。
###社内NuGet立ててがしがしライブラリを書く
「技術レベルの底上げ」「コード再利用の促進」と称してNuGetリポジトリを立て,コード規約を定め,ここからライブラリを使うように誘導しました。そして自らよく使うであろうライブラリをプッシュし続けています。
###Windows先行で開発,機能ロック
Windows先行で開発するのは単純にVisualStudioを使いたいがためです。XamarinStudioでのデバッグが辛い場面があるので,ライブラリとWindowsUIの開発,ユニットテストまではVisualStudioで行っています。特に非同期周りでステップインできなかったり,スタックトレースが遅くて例外発報まで時間がかかったりいろいろ……。
ここで機能ロックした内容をXamarin.MacでMac版にします。ほとんどUI層をなんとかするだけなので,迅速にMac版を展開できます。
###MonoコンソールプログラムでLinuxにも展開
コアのライブラリを完全に使い回せるので,Linux版も容易に作り出せます。FedoraでMonoDevelopを使い,同じソリューションを開くことができます。UIもGtk#で書けますが,今のところあまりUIを求められていないので追っていません。
これらを継続した結果,来年度もXamarin.Mac Businessを更新できる目処が立ちました。
##C#+Macだからこそハマる
こちらはフィットするという意味ではありません。
###Mono!=.NET Framework
あくまで同じようなランタイムというだけで,細部はプラットフォームごとにかなり異なります。全てのプラットフォーム上でユニットテストが通過することを確認しなければなりません。
特にファイルパスやI/O周りはドはまりポイントです。たとえば以下のコードは.NET FrameworkとMonoで動作が異なります。暇があればお試しください。
const int BufferSize = 1 << 20;
static async Task<byte[]> CopyFileAsync(FileInfo source, FileInfo destination)
{
if (destination.Directory != null)
destination.Directory.Create();
using (var md5 = MD5.Create())
{
using (var srcFs = new FileStream(
source.FullName, FileMode.Open, FileAccess.Read, FileShare.Read,
BufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan))
using (var dstFs = new FileStream(
destination.FullName, FileMode.Create, FileAccess.Write, FileShare.None,
BufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan))
{
var readBuffer = new byte[BufferSize];
var writeBuffer = new byte[BufferSize];
var bytesRead = await srcFs.ReadAsync(readBuffer, 0, readBuffer.Length);
while (bytesRead > 0)
{
Swap(ref readBuffer, ref writeBuffer);
var writer = dstFs.WriteAsync(writeBuffer, 0, bytesRead);
var reader = srcFs.ReadAsync(readBuffer, 0, BufferSize);
md5.TransformBlock(writeBuffer, 0, bytesRead, null, 0);
bytesRead = await reader;
await writer;
}
md5.TransformFinalBlock(readBuffer, 0, bytesRead);
}
destination.CreationTime = source.CreationTime;
destination.LastWriteTime = source.LastWriteTime;
destination.Attributes = source.Attributes;
return md5.Hash;
}
}
この問題はサポート経由でBugzillaに登録されています。Monoの問題でもXamarin.Macの問題でもとりあえずサポートに投げることができるので,Businessライセンスはかなり価値があります。
ちなみに上記のコードはStreamをFile.OpenRead
およびFile.OpenWrite
で作れば問題なく動きます。あとWriteAsync
の代わりにBeginWrite``EndWrite
を使うことでも回避可能です。ただしパフォーマンスが変わったりするのでなかなか一筋縄には。
###辛いNuGet
Xamarin.MacでNuGetを使うと,大抵Windowsと同じものが落ちてきて,たまに依存関係で死にます。Xamarin.Mac/MonoMac対応を明言しているライブラリは,NuGetにあってもだいたいバイナリを落とすなり自分でビルドすることになります。
###OSXネイティブ開発の知識は必要
OSX環境独特のマナー(DataSourceパターンとか)を最低限把握しておく必要はあります。ネイティブOSX開発の情報を調べようとするとたいていiOS開発のものしか出てきませんので,これが一番辛いところです。
##それでもXamarin
Windows, Mac, Linux環境向けデスクトップアプリケーション,さらにサーバサイドまで全てC#/F#で統一できるというのは後のメンテナンス性やコード可読性の面から見ても,最高の環境です。
Xamarin.Mac はハマりどころも多く,バグもそれなりにあり,Xamarin社としてもあまり推してはいない感があります。が,サポートは丁寧ですしフォーラムでも濃い議論が行われています。投資に見合った価値は十分にあるはずです。