More Related Content More from Tomohiro Suzuki (17) Xamarin.iOS の仕組みを理解しよう!12. UIButton にイベントを設定する。
(Xamarin.iOS)
12
public override void ViewDidLoad()
{
base.ViewDidLoad();
var dismissViewButton = new UIButton();
dismissViewButton.TouchUpInside += DismissViewButton_TouchUpInside;
View.AddSubview(dismissViewButton);
}
void DismissViewButton_TouchUpInside(object sender, EventArgs e)
=> DismissViewController(true, null);
イベント
ハンドラー
イベントの
アタッチ
26. Pros and Cons of Xamarin.iOS
26
Objective-C
Runtime
iOS APIs
Mono Runtime
C# / .NET APIsBinding
Exported
membersCall
iOS Kernel
禁断の果実に手を出した!!
Pros
iOS の OEM Widgets, API をすべて利
用でき、しかも自由に機能拡張できる
攻めた設計。
プロトコル、Selector など、
Objective-C のコアな機能を触れる。
Cons
その代償として、細かい対応の積み上
げ(黒魔術)で抑え込めない、メモ
リーリークやイベントの破壊などの仕
様を抱えている。
相互運用を行わず、Objective-C ラン
タイムの中の閉じた世界とすれば、そ
れらの問題も起こらなかった。
API の相互運用に踏みこんだ
ことで、大きなメリットとデ
メリットを抱えた。
28. Wrapper type と User type
Xamarin.iOS における GC の挙動を考える上で、
2つの型の区別はとても重要になります。
28
public override void ViewDidLoad()
{
base.ViewDidLoad();
var button = new UIButton();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var button = new MyButton();
}
public class MyButton : UIButton
{
public string Id { get; set; }
}
Wrapper type
User type
29. Wrapper type とその寿命
マネージドの世界では、ネイティブ
オブジェクトのインスタンスへのハ
ンドルだけを持つ。
Wrapper type のインスタンスの寿命
は、ネイティブオブジェクトの寿命
とは無関係である。
Wrapper type のインスタンスは、マ
ネージドな世界にネイティブオブ
ジェクトへのハンドル以外には何も
保持していないので、GCの判断に
よっていつでも自由に解放できる。
もし、後で再びそのオブジェクトが
必要になった場合には Wrapper type
のインスタンスが再度作成される。
29
Managed Native
UIButton UIButtonhandle
何もない
Managed 側でしか
保持していないステートは無い
Managed 側で不要になれば
いつでも破棄できる
UIView や UIButton のような
Objective-C の型をラップしたもの
参照カウンター
メモリーリークしない!
30. User type とその寿命
ネイティブオブジェクトのインスタ
ンスへのハンドルの他に、マネージ
ドな世界だけで管理されている、何
らかの“ステート”を持つ。
User type のオブジェクトは マネー
ジドな世界にユーザーが定義したス
テートを含む可能性があるため GC
の判断によって自由に解放すること
はできず、必要な間はずっと生かし
続けることを保証する必要がある。
よって、マネージオブジェクトは強
力な GCHandle によって保持される。
30
Managed Native
MyButton UIButton
handle
field
property
Managed側でしか
保持していないステート
Managed側のステートを保ち
続けるためにインスタンスを
破棄できない
UIView や UIButton のような
Wrapper type を継承し派生した型で
Objective-C に対応する型が無いもの
参照カウンター
いつ参照が解放されるかがポイント
event
31. User type でマネージ側の参照が解放
される状況
31
解放される状況は3つ
① 他からの参照がない状況で Dispose が明示的に呼び出されたとき
② GC がオブジェクトを解放したとき
③ オブジェクトの Handle プロパティが変更されたとき
ただし注意事項あり
ネイティブオブジェクトの参照カウンタが1でその参照が対応するマネージオブジェクトによる参照
である場合にのみ発生します。(それ以外の時は、マネージオブジェクトに強力な GCHandle があ
るため、GC の解放処理はブロックされます。)
これはよくわからない(Handleの変更などしないので…)
33. その問題を解決するため、ユーザーが Dispose を呼び出した
とき、Xamarin.iOS は以下のような挙動をします。
マネージオブジェクトの参照を外す。(まだGCに回収はされてい
ない)
ただし、ネイティブオブジェクトの参照カウンタが0に達するま
で、ネイティブオブジェクトとマネージオブジェクトの間のリン
クは切断しない。(よってGCは回収できない)
これにより、ネイティブオブジェクトが存続している限り、
マネージオブジェクトを引き続き参照することができるよう
にしています。
33
① 他からの参照がない状況で Dispose が明示的に呼び出されたとき
36. メモリーリークする仕組み
36
public override void ViewDidLoad()
{
base.ViewDidLoad();
var dismissViewButton = new UIButton();
dismissViewButton.TouchUpInside += DismissViewButton_TouchUpInside;
View.AddSubview(dismissViewButton);
}
void DismissViewButton_TouchUpInside(object sender, EventArgs e)
=> DismissViewController(true, null);
イベントがアタッチされる
と自動的に User Type に
アップグレードされる。
メモリーリークしない
Wrapper Type としてイ
ンスタンス生成
UIButton
UIView
Controller
UIButton は
UIViewController の
イベントハンド
ラーを参照してい
るため、 UIView の
参照を持つ
参照
UIViewController は
自分の配下の
UIView の SubView
である UIButton の
参照を持つ
参照
38. User type の挙動についてのその他情報
User types のオブジェクトは、次のいずれかの条件が発生す
ると、ネイティブ <-> マネージオブジェクトのディクショ
ナリーからオブジェクトが削除されます。
ネイティブオブジェクトが dealloc されたとき(参照カウ
ンタが0になったため)。
マネージオブジェクトの Handle プロパティが変更された
とき(変更前の Handle 値とマネージオブジェクトの間の
リンクがディクショナリーから削除されます)。
38
39. User type の挙動についてのその他情報
monoランタイムは、User type の場合ネイティブの世界で
2つの情報を追跡し続けています。
マネージオブジェクトへの GCHandle
マネージオブジェクトの参照があるか外れているか。
monoランタイムはすでに GCHandle を保存する
Objective-C ivar を持っているので、別の ivar を作成しませ
ん。
monoランタイムはマネージオブジェクトの参照があるか
どうかを GCHandle の1ビットを使用して保存しています
(MANAGED_REF_BIT) 。
39
41. イベントをデタッチする
41
public override void ViewDidLoad()
{
base.ViewDidLoad();
var dismissViewButton = new UIButton();
dismissViewButton.TouchUpInside += DismissViewButton_TouchUpInside;
View.AddSubview(dismissViewButton);
}
void DismissViewButton_TouchUpInside(object sender, EventArgs e)
=> DismissViewController(true, () =>
{
dismissViewButton.TouchUpInside -= DismissViewButton_TouchUpInside;
});
イベントの
デタッチ
イベントの
アタッチ
42. Objective-C の流儀に則り Selector
を使う
42
public override void ViewDidLoad()
{
base.ViewDidLoad();
var dismissViewButton = new UIButton();
dismissViewButton.AddTarget(this,
new ObjCRuntime.Selector("dismissViewButtonEvent:"),
UIControlEvent.TouchUpInside);
View.AddSubview(dismissViewButton);
}
[Export("dismissViewButtonEvent:")]
void DismissViewButtonEvent(NSObject sender)
=> DismissViewController(true, null);
Selector
Selector が実行する
メソッド
[Export("dismissViewButtonEvent:")] これ何??
44. レジストラーとは
イベントのハンドリングなどに Selector を使った場合、
Objective-C ランタイムから、マネージドのメソッドをコールす
る必要があります。
レジストラーはマネージドのメンバーを Objective-C ランタイム
に公開する仕組みです。
(.NET のイベントハンドラーを使った場合も当然裏で Selector
に変換されています)
44
public override void ViewDidLoad()
{
base.ViewDidLoad();
var dismissViewButton = new UIButton();
dismissViewButton.AddTarget(this, new ObjCRuntime.Selector("dismissViewButtonEvent:"), UIControlEvent.TouchUpInside);
View.AddSubview(dismissViewButton);
}
[Export("dismissViewButtonEvent:")]
void DismissViewButtonEvent(NSObject sender)
=> DismissViewController(true, null);
レジストラー
46. static void native_to_managed_trampoline_9 (id self, SEL _cmd,
MonoMethod **managed_method_ptr, NSObject * p0, uint32_t token_ref)
{
/* 長いから省略 */
mono_runtime_invoke (managed_method, mthis, arg_ptrs, NULL);
/* 長いから省略 */
}
extern "C"
C# のハンドラーが Objective-C から
呼ばれる仕組み
XamariniOS-InternalSamples/MemoryNotLeakSample/obj/iPhone/Release/mtouch-cache/registrar.m
46
-(void) dismissViewButtonEvent:(NSObject *)p0
{
static MonoMethod *managed_method = NULL;
native_to_managed_trampoline_9 (self, _cmd, &managed_method, p0, 0x1B00);
}
Message により Objective-C の
メソッドが呼ばれる
Objective-C
trampoline により 引数を
マネージドに変換した後
で C# のメソッドを呼ぶ
47. ちなみに…
XamariniOS-InternalSamples/MemoryNotLeakSample/obj/iPhone/Release/mtouch-cache/registrar.m
47
static MTClassMap __xamarin_class_map [] = {
/* 省略 */
{
NULL, 0x700
/* #10 'MemoryNotLeakSample_Views_DismissViewButton' => */
/* 'MemoryNotLeakSample.Views.DismissViewButton, MemoryNotLeakSample' */,
(MTTypeFlags) (3) /* CustomType, UserType */
},
{
NULL, 0x800
/* #11 'MemoryNotLeakSample_Views_DisiplayAlertButton' => */
/* 'MemoryNotLeakSample.Views.DisiplayAlertButton, MemoryNotLeakSample' */,
(MTTypeFlags) (3) /* CustomType, UserType */
},
/* 省略 */
};
Wrapper Type と User Type もレジストラーで
コンパイル時に登録されています
49. プロトコトル
XamariniOS-InternalSamples/MemoryNotLeakSample/obj/iPhone/Release/mtouch-cache/registrar.m
49
プロトコルも同様にマネージ側の C# デリゲートメソッドを Objective-C ランタ
イムからコールします。
よって、Xamarin.iOS ではそのアプリで使われているプロトコルをコンパイル時に
解析して registrar.m で登録しています。
static const MTProtocolWrapperMap __xamarin_protocol_wrapper_map [] = {
{ 0x8904 /* UIKit.IUIAccessibilityIdentification */, 0x8A04 /* UIAccessibilityIdentificationWrapper
*/ },
{ 0xCF04 /* Foundation.INSObjectProtocol */, 0xD004 /* NSObjectProtocolWrapper */ },
{ 0xD604 /* Foundation.INSUrlSessionDataDelegate */, 0xD804 /* NSUrlSessionDataDelegateWrapper */ },
{ 0xDB04 /* Foundation.INSUrlSessionDelegate */, 0xDC04 /* NSUrlSessionDelegateWrapper */ },
{ 0xDF04 /* Foundation.INSUrlSessionTaskDelegate */, 0xE104 /* NSUrlSessionTaskDelegateWrapper */ },
};
Objective-C
50. プロトコトル
50
プロトコルを利用する場合、レジストラーの仕組みを使って、Objective-C ラン
タイムから、C# メソッドを呼び出すため、C# の世界では Abstract クラス また
は Interface にレジストラー情報が定義してあります。
デリゲート 1つのみ利用
デリゲート定義 Abstract クラスを継承する
デリゲート 2つ以上同時利用
デリゲート定義 Interface を実装する
Class A
[Export(“Method1”)]
Method 1
[Export(“Method2”)
Method 2
Abstract Class B
[Export(“Method1”)]
Method 1
[Export(“Method2”)
Method 2
継承
Class X
[Export(“Method3”)]
Method 3
[Export(“Method4”)]
Method 4
[Export(“Method5”)]
Method 5
[Export(“Method6”)]
Method 6
Interface Y
[Export(“Method3”)]
Method 3
[Export(“Method4”)]
Method 4
実装
Interface Z
[Export(“Method5”)]
Method 5
[Export(“Method6”)]
Method 6
実装
C# は多重継承できないので…
51. Required と Optional
51
iOS SDK によって準備されているプロトコルであれば事前にメンバーもわかって
いるので、簡単かと思いきや、問題があります。それは、Objective-C のプロト
コルでは、実装を強制するかどうかを選択できますが、C# のインターフェース
はすべてのメンバーの実装が強制されます。
@protocol AVCapturePhotoCaptureDelegate <NSObject>
@optional
- (void)captureOutput:(AVCapturePhotoOutput *)output
willBeginCaptureForResolvedSettings:(AVCaptureResolvedPhotoSettings
*)resolvedSettings;
- (void)captureOutput:(AVCapturePhotoOutput *)output
willCapturePhotoForResolvedSettings:(AVCaptureResolvedPhotoSettings
*)resolvedSettings;
/* 長いから省略 */
@end
Objective-C
実装必須ではない。
この部分で、C#の対応
メソッドを判断する
52. プロトコルの定義 Optional の場合
52
[Introduced(PlatformName.iOS, 10, 0, PlatformArchitecture.All, 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 })]
// 省略
public interface IAVCapturePhotoCaptureDelegate : INativeObject, IDisposable
{
}
C# インターフェース
実装必須ではない。
実装必須メソッドが存在しないため、
インターフェース本体内は空!!
実装するときの
C# のメソッド名
プロトコル名
53. プロトコルの定義 Required の場合
53
[Introduced(PlatformName.iOS, 11, 0, PlatformArchitecture.All, null)]
[Protocol(Name = "UIDataSourceTranslating", WrapperType = typeof(UIDataSourceTranslatingWrapper))]
[ProtocolMember(IsRequired = true, IsProperty = false, IsStatic = false,
Name = "GetPresentationSectionIndex", Selector = "presentationSectionIndexForDataSourceSectionIndex:",
ReturnType = typeof(nint), ParameterType = new[] { typeof(nint) }, ParameterByRef = new[] { false })]
[ProtocolMember(IsRequired = true, IsProperty = false, IsStatic = false,
Name = "GetDataSourceSectionIndex", Selector = "dataSourceSectionIndexForPresentationSectionIndex:",
ReturnType = typeof(nint), ParameterType = new[] { typeof(nint) }, ParameterByRef = new[] { false })]
// 省略
public interface IUIDataSourceTranslating : INativeObject, IDisposable
{
[BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
[Export("presentationSectionIndexForDataSourceSectionIndex:")] [Preserve(Conditional = true)]
nint GetPresentationSectionIndex(nint dataSourceSectionIndex);
[BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
[Export("dataSourceSectionIndexForPresentationSectionIndex:")] [Preserve(Conditional = true)]
nint GetDataSourceSectionIndex(nint presentationSectionIndex);
// 省略
}
C# インターフェース
実装必須メソッドは、
インターフェース本体
内に定義される
実装必須
実装すると
きの C# の
メソッド名
プロトコル名
58. 自分個人として Xamarin やってよかっ
たこと
58
① 言語に関係ないプログラム言語についての理解がとても深まった
② iOS, .NET の思想の違いを理解し、いい感じに移植できるスキルを
身に付けることができた。
③ iOSDC でトークできた!!
とんでもなく大変な目に会って、絶対に Swift を学んだほうが早かったが、開
発者としてのスキルはレベルアップできた。楽しかったのでOK。
希少なスキルのため、指名でお仕事をすることもでき
た。これも楽しかったのでOK。
身に付けた知見を発表する場を頂けて、とても楽
しかったのでOK。
59. ここがすごいよ Xamarin
59
① ネイティブの API を100% 叩けてDRY(Don’t Repeat Your
Self)が実現できるクロスプラットフォーム開発インフラである。
② C#で構築済みのビジネスロジックがある場合、ほとんど手直しな
しでそのまま使え、スキル保持者も少ないので「利益」を確保できる。
③ Visual Studio(IDE), Azure(Cloud), Azure DevOps(CI/CD) などのエコ
システムが整っている。特に Azure DevOps は Swift, Objective-C でも
普通に使えとても便利。
④ すべての地雷を理解して回避し、使いこなせるようになった時の満
足感が半端ない!
60. Xamarin どう使ったらいいの?
60
③ 選択肢の一つとして、適材適所で使い分けするのが良い。
④ Xamarin に限らず、Objective-C, Swift, Flutter, React Native, Kotlin /
Native などできるだけ多くを体験したい。
例えば、Microsoft だからと言って Xamarin を使うわけではなく、彼ら自身も使
い分けている。iOS, Android の Word や Excel などの Office 365 アプリの UI
は React Native で開発されている。
別の視点からプラットフォームを見る
ことで、見識が深まります。
① 「楽をするため」のツールではない!!あくまでも、効果も問題点
もすべてを理解して使うプロ向けのツール。 楽をしようとして使うと、大変な目
に会う。
② ロジックの占める割合が多ければ、C#er にはとても入りやすい。