ニュートラルカルチャ名から任意の特定のカルチャ名を得る

ニュートラルカルチャ名から任意の特定のカルチャ名を得るにはCultureInfo.TextInfo.CultureNameを見ればよい。
例えば英語(en)ならアメリカ英語(en-US)、日本(ja)なら日本語(ja-JP)を取得するというもの。

The CultureName property always reflects a specific culture rather than a neutral culture. If CultureInfo.Name has a neutral culture as its value, then the corresponding CultureName has as its value an arbitrary specific culture that uses the same language.

http://msdn.microsoft.com/en-us/library/system.globalization.textinfo.culturename%28v=vs.100%29.aspx

この機能がMono Frameworkではまだ実装されていなかった。

System.Diagnostics.Debug.WriteLine((new System.Globalization.CultureInfo("en")).TextInfo.CultureName);
// -> "en-US"

System.Console.WriteLine((new System.Globalization.CultureInfo("en")).TextInfo.CultureName);
// -> System.ArgumentException: Region ID 9 (0x0009) is not a supported region.

以前、Mono FrameworkでニュートラルカルチャなCultureInfoクラスはインスタンス化できなかったが最近になってできるようになった。しかしまだ対応できていない所があるようだ。

Xamarin.iOSでInstrumentsを使わずにアプリの使用メモリ量を取得する

アプリの使っているメモリ量はInstrumentsを使うと詳しく調査できるのだが、
パフォーマンスが悪かったりコードの特定の地点での値を得るのが難しい。
こういう場合本家ではtask_info()を使用してtask_basic_info構造体を取得すればよいのだが、Xamarin.iOSではまだバイディングされていないようなので自前でバイディングしてみた。

using System;
using System.Runtime.InteropServices;

static class Diag
{
  public const int TASK_BASIC_INFO = 4;

  public struct TimeValue
  {
    public int Seconds;
    public int Microseconds;
  }

  public struct TaskBasicInfo
  {
    public int SuspendCount;
    public uint VirtualSize;
    public uint ResidentSize;
    public TimeValue UserTime;
    public TimeValue SystemTime;
    public int Policy;
  }

  [DllImport("/usr/lib/system/libsystem_kernel.dylib", CallingConvention = CallingConvention.Cdecl)]
  public static extern uint mach_task_self();

  [DllImport("/usr/lib/system/libsystem_kernel.dylib", CallingConvention = CallingConvention.Cdecl)]
  public static extern int task_info(uint targetTaskID, int flavor, ref TaskBasicInfo taskInfo, ref int size);

  public static TaskBasicInfo GetTaskInfo()
  {
    TaskBasicInfo taskInfo = new TaskBasicInfo();
    uint taskid = mach_task_self();
    int size = Marshal.SizeOf(typeof(TaskBasicInfo));
    task_info(taskid, TASK_BASIC_INFO, ref taskInfo, ref size);
    return taskInfo;
  }
}

※このコードではTaskBasicInfo.UserTimeとTaskBasicInfo.SystemTimeが取得できない。おそらくどこか間違っているんだろう。

ad-hoc用IPAパッケージを再ビルドしないで修正してみた

業務的に同一のソースコードからBundle IdentifierやAppアイコン、スプラッシュイメージが異なるだけのIPAパッケージをいくつも作る必要があった。
今までは差分ファイルを切り替えるシェルスクリプトと自動ビルドさせるシェルスクリプトを組み合わせてパッケージの数だけ再ビルドを行っていた。
これがソースコードに変更がないのであれば既存のIPAパッケージを元に、再ビルド無しにパッケージを作ることが可能だというのでやってみた。

やりたいこと

既存のIPAパッケージを元に、

  • Appアイコン、スプラッシュイメージの差し替え
  • Appタイトルの変更(Sample1 -> Sample2)
  • Bundle Identifierの変更(com.myapp.sample2 -> com.myapp.sample2)
  • 別のプロビジョニングプロファイルに切り替える
  • 別の署名に切り替える
必要な物
  • Xcode
  • IPAパッケージ
  • codesignコマンド(Mac OS付属)
  • 別のプロビジョニングプロファイルのファイル(myprovision2.mobileprovision)
  • 別のCode Signing Identityの名前(iPhone Distribution: MyDistribution2)
  • 別のAppアイコンとスプラッシュイメージ
手順

1.適当なフォルダにIPAパッケージをunzipする
以下の様なフォルダ構造で解凍される

Payload
└Sample.app
  ├Info.plst
  ├Default.png
  └(略)
iTunesMetadata.plist

2.xcodeでPayload/Sample.app/Info.plistファイルを編集して保存
3.Payload/Sample.app内のAppアイコン、スプラッシュイメージを差し替える
4.myprovision2.mobileprovisionファイルをembedded.mobileprovisionという名前でPayload/Sample.app以下に上書きコピー
5.xcodeでiTunesMetadata.plistを編集してInfo.plistと合わせておく

6.ターミナルを起動して以下のコマンドを実行

export EMBEDDED_PROFILE_NAME=embedded.mobileprovision
export CODESIGN_ALLOCATE="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate"
codesign -f -s -vv "iPhone Distribution: MyDistribution2" -i "com.myapp.sample2" ./Payload/Sample.app

環境変数EMBEDDED_PROFILE_NAMEとCODESIGN_ALLOCATEはcodesignが必要とする


7.PayloadフォルダとiTunesMetadata.plistをまとめてzip
8.zipしたファイルの拡張子ipaに変更


その他
  • Xcodeはv5.x使ってます
  • Xamarin.iOSでビルドしたパッケージでも可能
  • パッケージ内のInfo.plistはビルド時に付加情報が書き込まれてあるので、ビルドを通していないInfo.plistを上書きコピーなどして使ってはいけない。もし使ってしまった場合はインストールに失敗するパッケージになる(codesignコマンドでエラーはでない)。
  • Info.plistファイルは元々バイナリ形式になっているが、XML形式でも問題はおきない。
  • 差し替える画像(PNG)は最適化してなくても問題はおきない。
  • 言語リソース等も差し替え可能。

SMB2の不具合?

何時の頃からか秀丸でファイル共有先のファイルを編集していると「このファイルは、他のアプリケーションによって書き換えられてしまいました。」の旨のダイアログが表示されるようになってしまった。
サーバー側(共有先)で編集中のファイルのタイムスタンプを定期的に表示していると、秀丸で保存後約13秒後に誰かがファイルを更新している。
更新しているのは誰なのかProcessMonitorやらNetworkMonitorで調べていると、
SMB2のCLOSE要求がきっかけでファイルの更新が発生していることが判った。
コレがかなり怪しいということでSMB2のよくあるトラブルシューティングをしてみた。
まずクライアント側の各種キャッシュの無効化。これは効果がなかった。
次にサーバー側でSMB2を無効化したら問題が解消された。
たしかに問題は解決したが共有先全てでSMB2を無効にするのはかなり無理がある。
どうしたものか。

iOS7.1から企業内アプリ配布にSSLが必須となったので自己証明書でなんとかしてみた

突然社内へのiOSアプリ配信が動かなくなったと連絡を受けた。
原因はこれ
iOS7.1からplistファイルの配布にhttpsが必須になっていた。
しかし社内向けのしかもテスト用の配信システムににわざわざ予算を掛けてまでSSL証明書など買っていられないので自己証明書でなんとかならないかやってみた。

IISで作る自己証明書はiOSと互換性がない

配信サーバーがIISであることから、まずIISの自己証明書作成機能をつかってみた。opensslコマンドでpfx->pem->cerなど形式を変換しつつiOSに自己証明書をインストールしてみた。しかし相変わらずアプリの配信ができない。よくよく調べてみるとIISで作る自己証明書はiOSと互換性がないので使えないとのことだった。

MacのopensslでCAから作ってみる

結局ネットでみつけたここのTip #5、自己CAから自己証明書を発行して~の手順でうまくいった。
Macのopensslコマンドで各種証明書を生成。それらをIISiOSデバイスに取り込んだ。

手順

1.自己CAのための秘密鍵を生成

openssl genrsa -out myserverCA.key 2048


2.自己CA証明書の生成(有効期限730日)

openssl req -x509 -new -key myserverCA.key -out myserverCA.cer -days 730 -subj /CN="Myserver Custom CA"

できあがるmyserverCA.cerが自己CA証明書になる。


3.自己証明書のための秘密鍵を生成

openssl genrsa -out myservercert1.key 2048


4.自己証明書を生成するためのリクエストを生成

openssl req -new -out myservercert1.req -key myservercert1.key -subj /CN=myserver.example.com

元々http://myserver.example.com/iOShogehoge...というURLだったのでCNはmyserver.example.comとなる。


5.自己証明書を生成するためのリクエストを承認して自己証明書を生成(有効期限365日)

openssl x509 -req -in myservercert1.req -out myservercert1.cer -CAkey myserverCA.key -CA myserverCA.cer -days 365 -CAcreateserial -CAserial serial

生成されたmyservercert1.cerが自己証明書になる。


6.IISで読み込むためにcer形式をpfx形式に変換

openssl pkcs12 -export -out myserver.selfsigned.pfx -inkey myservercert1.key -in myservercert1.cer

7.myserverCA.cerとmyserver.selfsigned.pfxをIISのサーバーにコピー

8.myserverCA.cerをダブルクリックで証明書ストアにインポート

9.IIS管理コンソールのツリーからサーバー名を選択してサーバー証明書ビューからmyserver.selfsigned.pfxをインポート
インポートした証明書は名前欄が空白になってしまった。生成時のオプション不足?

10.IIS管理コンソールのツリーからWebサイトを選択してバインドメニューからhttpsを追加
この時先程インポートした証明書を使うようにする。

11.iPhone構成ユーティリティでmyserverCA.cerを含んだ構成プロファイルを生成し署名ありでエクスポート
ファイル名はmyserver-ssl.mobileconfigにした。

12.エクスポートしたmyserver-ssl.mobileconfigをIISにコピーしてhttpでアクセスできるようにハイパーリンクなどを追加する

13.IISMIMEタイプに拡張子".mobileconfig"、種類"application/x-apple-aspen-config"を追加

14.plistファイルへのリンクのhttpをhttpsに変更する

15.デバイスのSafariからmyserver-ssl.mobileconfigへのリンクを開いてデバイスに自己CA証明書をインストール
この時証明されていない旨の表示がされるがインストールはできる。



これでiOS7.1でも警告無くアプリがインストール出来るようになった。
しかしこの方法にはデメリットがあって問題の無かったiOS7.0.x以前でも自己証明書のインストールが必要になってしまった。javascriptでごにょごにょすれば回避できるのだろうが面倒くさいのそこまではしなかった。

ステータスバーのバッテリーアイコン付近の色情報を取得してみた

ステータスバーのバッテリーアイコン付近の色情報(RGBA)を取得する必要があって、そのときに書いたコード。
処理的にはスクリーンキャプチャしたCGImageから対象の領域を1pxにトリミングして、そこからRGBA情報を取得するというもの。

using System;
using System.Drawing;
using MonoTouch.UIKit;
using MonoTouch.CoreGraphics;



CGImage screenImage;
CGImage captureImage;

screenImage = CGImage.ScreenImage;
float width = screenImage.Width;
float height = screenImage.Height;

switch (UIDevice.CurrentDevice.Orientation)
{
  case UIDeviceOrientation.LandscapeLeft:
    captureImage = screenImage.WithImageInRect(new RectangleF(width - 4, height - 4, 1, 1));
    break;
  case UIDeviceOrientation.PortraitUpsideDown:
    captureImage = screenImage.WithImageInRect(new RectangleF(4, height - 4, 1, 1));
    break;
  case UIDeviceOrientation.LandscapeRight:
    captureImage = screenImage.WithImageInRect(new RectangleF(4, 4, 1, 1));
    break;
  default:
    captureImage = screenImage.WithImageInRect(new RectangleF(width - 4, 4, 1, 1));
    break;
}

CGDataProvider dataProv = captureImage.DataProvider;

byte r;
byte g;
byte b;
byte a;
unsafe
{
  IntPtr intPtr = dataProv.CopyData().Bytes;
  byte* byteData = (byte*)intPtr;
  r = byteData[0];
  g = byteData[1];
  b = byteData[2];
  a = byteData[3];
  Console.WriteLine("r={0}, b={1}, b={2}, a={3}", r, g, b, a);
}

画像データのビットマップ情報に直接アクセスできるメソッドやプロパティはないようで、unsafeなコードでポインタ上から直接RGBAの情報にアクセスしなければならなかった。

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

解決できた。前回の記事の追記にて見つけたページにヒントがあった。Dispatch内でRunLoopは扱えないとのこと。これをふまえて修正したコードはこのようになった。

DispatchQueue.GetGlobalQueue(DispatchQueuePriority.Low).DispatchAsync(delegate {
  DispatchQueue.MainQueue.DispatchAsync(delegate () {
    NSTimer.CreateScheduledTimer(new TimeSpan(1), 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));
      }
    });
  });
});

Dispatch内でRunLoopが扱えないのだったら、逆にDispatch内からNSTimer経由で呼び出したらどうだろうという発想。これが正しいのかどうかは判らないが問題を回避できたのでとりあえずこのまま行ってみようと思う。