Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

UIImageWriteToSavedPhotosAlbumを呼んでコールバックしてもらう。

正攻法だと呼んでもらえないという話があったので、さくっと要点だけ。テスト環境はXE4です。

原因はRTLのバグ

バグです。Macapi.ObjectiveCユニットのMangleType関数がおかしいです。
本来は色々と用意さえすれば、(TOCLocalを継承したクラスなら)自身の登録からメソッドの登録までRTLが勝手にやってくれるんですが、バグってるのであてにできません。なので自前でやればOKです。

自前でメソッドを登録する

まずTOCLocalを継承したクラスを作ります。ぶっちゃけ継承していればどうなっててもいいです。TOCLocalを継承しない方法もありますが、それはまたいつか。
次に登録するメソッドを作ります。作りますが、このクラスのメソッドにはしません。
詳細は省きますが、Delphiのメソッドに暗黙の第一引数Selfがあるように、Objective-Cのメソッドにも暗黙の引数があります。しかも2個もあります。当然ながらDelphiのメソッドとは互換性がないわけで、実行時に引数を変換するめんどくさい作業をしないといけないんですが、そんなことするくらいなら暗黙の引数を明示的に書いてやれば変換も不要です。やったね。
というわけでこれが実装です。

type
  THoge = class(TOCLocal)
  public
    constructor Create;
  end;

procedure hogehoge(self: Pointer; _cmd: SEL; image: Pointer;
  didFinishSavingWithError: Pointer; contextInfo: Pointer); cdecl;
var
  img: UIImage;
  error: NSError;
begin
  img := TUIImage.Wrap(image);
  error := TNSError.Wrap(didFinishSavingWithError);
  // ごにょごにょ
end;

constructor THoge.Create;
var
  M: TMarshaller;
  cls: Pointer;
  methodName, methodTypes: string;
begin
  inherited;
  methodName := 'image:didFinishSavingWithError:contextInfo:';
  methodTypes := 'v@:@@^v';
  cls := object_getClass(GetObjectID);
  class_addMethod(cls, sel_getUid(M.AsAnsi(methodName, CP_UTF8).ToPointer), @hogehoge, M.AsAnsi(methodTypes, CP_UTF8).ToPointer);
end;

はい、以上です。
あとはUIImageWriteToSavedPhotosAlbumを呼ぶ際に、completionTargetとして上のクラスのGetObjectID、completionSelectorに上で登録したのと同じセレクタを渡せばOKです。contextInfoにはフォームあたりのSelfなんかを渡しておくと便利だと思います。
あとコールバック内でShowMessageを呼ぶと固まります。これはCocoa側の仕様なのか、Delphiがまた何かやらかしてるのかは不明ですが、やらない方が賢明です。ちなみに僕がテストした時は、コールバック内でNSObjectの持つperformSelectorメソッドを使って、0秒後にShowMessageを遅延実行するようにしたら大丈夫でした。参考まで。

解説

気が向いたらちゃんと書きますが、ざっくり言うとmethodTypesに入れる文字列を作る関数(MangleType)がバグってるのでいろいろとダメです。ていうかMangleTypeさんやる気なさすぎです。Clangだとコア部分だけで何百行と使って書かれてるのに、数十行って。数十行って…。他にもダメな部分があってそれが関与してる可能性はありますが、検証がめんどくさいのでこれ作って給料もらってる奴が調べればいいと思います。
あ、methodTypesどうやって書いたらええねんというのは、Appleが資料を公開してるので適当に読みましょう。
Objective-C Runtime Programming Guide: Type Encodings
あるいはそこにも書いてあるように、Objective-Cで@encodeというコンパイラディレクティブを使うことで簡単に得られます。

本日のまとめ

IDEに負けず劣らずバグってますね!