個人的なメモ 〜Cocos Sharp 情報を中心に‥

Tomohiro Suzuki @hiro128_777 のブログです。Cocos Sharp の事を中心に書いています。 Microsoft MVP for Visual Studio and Development Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

Swift, Objective-C を Xamarin.iOS に移植する際のポイント(1) デリゲート その1

はじめに

こんにちは、@hiro128_777です。

10月14日(土)と、しばらく先の話になりますが、Xamarin.iOS のハンズオンを開催いたします!

今まで、Windows の開発を行なっており Xcode での開発経験がない方が、iOS の開発を Xamarin で始めてWebで情報を集めると、 Swift や Objective-C の情報はたくさん見つかりますが、Xamarin の情報は案外少ないことに気づきます。
そして、Swift や Objective-C のサンプルコードを見て、Xamarin に移植する必要が出てきますが、これは Xcode での開発経験がないと結構骨が折れる作業です。
そこで今回、Swift, Objective-C のコードを Xamarin.iOS に移植する際のポイントについてハンズオンを行うことにいたしましたので、ご興味がある方はぜひご参加下さい!

jxug.connpass.com


また、ハンズオン開催に先立って本ブログでも今回から数回にわたり、Swift, Objective-C を Xamarin.iOS に移植する際のポイントをご説明します。

初回である今回は、デリゲートについてご説明します。

C#のデリゲートとiOSのデリゲートの違いについて

C#のデリゲートは安全にカプセル化された関数ポインタのようなもので、言語仕様として準備されている機能ですが、Swiftのデリゲートは言語仕様ではなくデザインパターンであり、設計手法の一つです。よって利用目的は似てるのですが、文法的にはまったく違うものです。

この違いがわかりにくいので、今回はデリゲートについてご説明します。

Xamarin.iOS は、Swift ではなく、Objective-C に寄った設計です。

このことが、Swift のコードを Xamarin.iOS へ移植する際のハードルになっています。もちろん、Swift, Objective-C の開発経験があれば簡単なのですが、Windows の世界から来た方にはなかなか大変です(私もいつも苦労しています)。

プロトコルと Xamarin.iOS

標準ライブラリで定義済みのデリゲートは、プロトコルと一緒に利用されます。

プロトコルは、C#の世界のインターフェースとほぼ同じと考えていただいて結構です。iOSの世界では、標準ライブラリに様々なプロトコルが定義されています。

では、標準ライブラリのプロトコルを利用したデリゲートの実装を詳しく見ていきましょう。

今回は写真撮影のサンプルアプリを題材にします。

以下よりサンプルコードをダウンロードして下さい。
developer.apple.com

その中の、PhotoCaptureDelegate.swiftファイルのコードを見てみましょう。
クラスの定義部分に注目すると、

class PhotoCaptureProcessor: NSObject {
extension PhotoCaptureProcessor: AVCapturePhotoCaptureDelegate {


とあります。

ここに出てきているAVCapturePhotoCaptureDelegateは定義済みのプロトコルです。

Swift でAVCapturePhotoCaptureDelegateのように定義済みのプロトコルが利用されている場合、基本的には Xamarin.iOS 側には対応する interface および class が準備されています。
interface は Swift のプロトコルの名前の頭に 「I」 を付けた名前、class は Swift の Protocol の名前と同名になっています。

例えば、Swift のAVCapturePhotoCaptureDelegateに対応する Xamarin.iOS の interface はIAVCapturePhotoCaptureDelegate、class はAVCapturePhotoCaptureDelegateです。

では、IAVCapturePhotoCaptureDelegateのメタ情報を見てみましょう

namespace AVFoundation
{
	[Introduced(PlatformName.iOS, 10, 0, PlatformArchitecture.None, null)]
	[Protocol(Name = "AVCapturePhotoCaptureDelegate", WrapperType = typeof(AVCapturePhotoCaptureDelegateWrapper))]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "WillBeginCapture", Selector = "captureOutput:willBeginCaptureForResolvedSettings:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(AVCaptureResolvedPhotoSettings) }, ParameterByRef = new[] { false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "WillCapturePhoto", Selector = "captureOutput:willCapturePhotoForResolvedSettings:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(AVCaptureResolvedPhotoSettings) }, ParameterByRef = new[] { false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "DidCapturePhoto", Selector = "captureOutput:didCapturePhotoForResolvedSettings:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(AVCaptureResolvedPhotoSettings) }, ParameterByRef = new[] { false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "DidFinishProcessingPhoto", Selector = "captureOutput:didFinishProcessingPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(CMSampleBuffer), typeof(CMSampleBuffer), typeof(AVCaptureResolvedPhotoSettings), typeof(AVCaptureBracketedStillImageSettings), typeof(NSError) }, ParameterByRef = new[] { false, false, false, false, false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "DidFinishProcessingRawPhoto", Selector = "captureOutput:didFinishProcessingRawPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(CMSampleBuffer), typeof(CMSampleBuffer), typeof(AVCaptureResolvedPhotoSettings), typeof(AVCaptureBracketedStillImageSettings), typeof(NSError) }, ParameterByRef = new[] { false, false, false, false, false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "DidFinishRecordingLivePhotoMovie", Selector = "captureOutput:didFinishRecordingLivePhotoMovieForEventualFileAtURL:resolvedSettings:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(NSUrl), typeof(AVCaptureResolvedPhotoSettings) }, ParameterByRef = new[] { false, false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "DidFinishProcessingLivePhotoMovie", Selector = "captureOutput:didFinishProcessingLivePhotoToMovieFileAtURL:duration:photoDisplayTime:resolvedSettings:error:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(NSUrl), typeof(CMTime), typeof(CMTime), typeof(AVCaptureResolvedPhotoSettings), typeof(NSError) }, ParameterByRef = new[] { false, false, false, false, false, false })]
	[ProtocolMember(IsRequired = false, IsProperty = false, IsStatic = false, Name = "DidFinishCapture", Selector = "captureOutput:didFinishCaptureForResolvedSettings:error:", ParameterType = new[] { typeof(AVCapturePhotoOutput), typeof(AVCaptureResolvedPhotoSettings), typeof(NSError) }, ParameterByRef = new[] { false, false, false })]
	[Unavailable(PlatformName.WatchOS, PlatformArchitecture.All, null)]
	[Unavailable(PlatformName.TvOS, PlatformArchitecture.All, null)]
	[Unavailable(PlatformName.MacOSX, PlatformArchitecture.All, null)]
	public interface IAVCapturePhotoCaptureDelegate : INativeObject, IDisposable
	{
	}
}

かなりごちゃごちゃしていますが、悲しいことに全て非常に重要な情報です(笑)。特にNameSelectorなどの情報に注目してください。


次に、AVCapturePhotoCaptureDelegateのメタ情報を見てみましょう

namespace AVFoundation
{
	[Introduced(PlatformName.iOS, 10, 0, PlatformArchitecture.None, null)]
	[Model]
	[Protocol]
	[Register("AVCapturePhotoCaptureDelegate", false)]
	[Unavailable(PlatformName.WatchOS, PlatformArchitecture.All, null)]
	[Unavailable(PlatformName.TvOS, PlatformArchitecture.All, null)]
	[Unavailable(PlatformName.MacOSX, PlatformArchitecture.All, null)]
	public class AVCapturePhotoCaptureDelegate : NSObject, IAVCapturePhotoCaptureDelegate, INativeObject, IDisposable
	{
		[CompilerGenerated]
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		[Export("init")]
		public AVCapturePhotoCaptureDelegate();

		[CompilerGenerated]
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected AVCapturePhotoCaptureDelegate(NSObjectFlag t);

		[CompilerGenerated]
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		protected internal AVCapturePhotoCaptureDelegate(IntPtr handle);

		[CompilerGenerated]
		[Export("captureOutput:didCapturePhotoForResolvedSettings:")]
		public virtual void DidCapturePhoto(AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings);

		[CompilerGenerated]
		[Export("captureOutput:didFinishCaptureForResolvedSettings:error:")]
		public virtual void DidFinishCapture(AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings, NSError error);

		[CompilerGenerated]
		[Export("captureOutput:didFinishProcessingLivePhotoToMovieFileAtURL:duration:photoDisplayTime:resolvedSettings:error:")]
		public virtual void DidFinishProcessingLivePhotoMovie(AVCapturePhotoOutput captureOutput, NSUrl outputFileUrl, CMTime duration, CMTime photoDisplayTime, AVCaptureResolvedPhotoSettings resolvedSettings, NSError error);

		[CompilerGenerated]
		[Export("captureOutput:didFinishProcessingPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:")]
		public virtual void DidFinishProcessingPhoto(AVCapturePhotoOutput captureOutput, CMSampleBuffer photoSampleBuffer, CMSampleBuffer previewPhotoSampleBuffer, AVCaptureResolvedPhotoSettings resolvedSettings, AVCaptureBracketedStillImageSettings bracketSettings, NSError error);

		[CompilerGenerated]
		[Export("captureOutput:didFinishProcessingRawPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:")]
		public virtual void DidFinishProcessingRawPhoto(AVCapturePhotoOutput captureOutput, CMSampleBuffer rawSampleBuffer, CMSampleBuffer previewPhotoSampleBuffer, AVCaptureResolvedPhotoSettings resolvedSettings, AVCaptureBracketedStillImageSettings bracketSettings, NSError error);

		[CompilerGenerated]
		[Export("captureOutput:didFinishRecordingLivePhotoMovieForEventualFileAtURL:resolvedSettings:")]
		public virtual void DidFinishRecordingLivePhotoMovie(AVCapturePhotoOutput captureOutput, NSUrl outputFileUrl, AVCaptureResolvedPhotoSettings resolvedSettings);

		[CompilerGenerated]
		[Export("captureOutput:willBeginCaptureForResolvedSettings:")]
		public virtual void WillBeginCapture(AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings);

		[CompilerGenerated]
		[Export("captureOutput:willCapturePhotoForResolvedSettings:")]
		public virtual void WillCapturePhoto(AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings);
	}
}

こちらにも、非常に重要な情報が含まれています。先ほどのIAVCapturePhotoCaptureDelegateNameSelectorなどの情報と、こちらのExportの情報を組み合わせて、コンパイル時にiOSのデリゲートが構成される仕組みになっています。深堀してみるとかなりの力技ですね(笑)

長くなりますので今回はここまでにします。

間違いなどございましたらご指摘お願いします。

次回は、具体的にデリゲートを含むコードをどう移植するのかご説明します。