Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

普通に書くと即メモリーリーク!こんなに大変だけど、俺は Xamarin.iOS を使い続けるぜ!

3,326 views

Published on

Xamarin.iOS は気をつけないとすぐメモリーリークしてしまいます。なぜ、そのような事が起こるのか、Xamarin.iOS の仕組みを解説しています。
こちらの公式ドキュメントも是非ご参照ください。
https://docs.microsoft.com/ja-jp/xamarin/ios/internals/architecture?WT.mc_id=DT-MVP-5002467

Published in: Technology
  • Be the first to comment

普通に書くと即メモリーリーク!こんなに大変だけど、俺は Xamarin.iOS を使い続けるぜ!

  1. 1. 普通に書くと即メモリーリーク! こんなに大変だけど、 俺は Xamarin.iOS を使い続けるぜ! 9/5/2019 Thu 18:10~19:10 Tomohiro Suzuki @hiro128_777
  2. 2. まあとりあえず、あんた誰なの?? 鈴木友宏 NTTデータ先端技術株式会社で働いてます。 Xamarin.iOS が大好きです。 Xamarin.iOS を頑張って広めています。 Twitter @hiro128_777 2
  3. 3. ご質問 Xamarin.iOS 知ってるよ~! Xamarin.iOS を仕事、趣味問わず、 使ったことがあるよ~! 3
  4. 4. 本日お話しすること Xamarin.iOS の仕組みを時間の許す限り! 以上!(潔い!) 4
  5. 5. Xamarin.iOS とは Xamarin Native 5 Xamarin.Forms ロジックのみ共通化 UI はネイティブで個別に作りこむ ロジックと UI を共通化 UI は各プラットフォームの 同じ役割の UI が自動マッピング Shared C# App Logic (.NET Standard) iOS C# UI Android C# UI Windows C# UI Shared C# App Logic (.NET Standard) Shared C# UI Code (Xamarin.Forms) ここが Xamarin.iOS
  6. 6. ところで、なんでお前は、 Xamarin なんてイロモノを 使ったの?? 6
  7. 7. なんで、Xamarin.iOS なんてイロモノを 使ったの Objective-C が辛すぎて逃げたかった。 (Swift はリリースの直前の時期‥) 楽をしたい。 ずっと C# で Windows アプリを開発していた。 7 これがすべての 間違いの始まり!!
  8. 8. 8 Miguel Miguel de Icaza (Xamarin の産みの親) のファンだから
  9. 9. Miguel のどこがすごいの?? .NET Frameworkが発表されたとき「Linux に移植 できんじゃね?」と考え、mono (Linux で動作す る .NET の実行環境)の開発を開始した。 その後、OSS で開発を続け、何度も事業売却され たり、レイオフされてもあきらめず、最後は自分 で会社を作り、mono をベースにした Xamarin を 発表し、クロスプラットフォームを実現。現在は マイクロソフトの子会社となる。 9
  10. 10. Xamarin.iOS がどれくらい 簡単かお見せしましょう 10
  11. 11. Button にイベントを設定する。 (Windows) 11 void InitializeComponent() { var button = new Button(); button.Click += Button_Click; Controls.Add(button); } void Button_Click(object sender, EventArgs e) => DoSomething(); イベントハンドラー イベントの アタッチ
  12. 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); イベント ハンドラー イベントの アタッチ
  13. 13. なんでや!! 13
  14. 14. 14
  15. 15. メモリーリーク する仕組み 15
  16. 16. Xamarin.iOS で必要な知識 (私の希望) 16 ネイティブ Xamarin.iOS iOS API Swift, Objective-C Xcode API 言語 統合開発環境 iOS API Swift, Objective-C Xcode Visual StudioC# 夢のような世界!!すばらしい! ❌ ❌
  17. 17. Xamarin.iOSで必要な知識 (現実) 17 覚えること増えただけやん!! ネイティブ Xamarin.iOS iOS API Swift, Objective-C Xcode API 言語 統合開発環境 iOS API Swift, Objective-C Xcode Visual StudioC#
  18. 18. 始める前に勝手に想像していた Xamarin.iOS の仕組み ipa 18 iOS APIs C# / .NET APIs 簡単に呼べる C# からチョー簡単に P/Invoke で呼べる。わーい!わーい!
  19. 19. 実際の Xamarin.iOS の仕組み ipa 19 Objective-C Runtime iOS APIs 黒魔術 C# / .NET APIs黒魔術 黒魔術黒魔術 iOS Kernel 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術 黒魔術
  20. 20. 20
  21. 21. 本当の Xamarin.iOS の仕組み ipa 21 Objective-C Runtime iOS APIs Mono Runtime C# / .NET APIsBinding Exported members Call iOS Kernel
  22. 22. Pros and Cons of Xamarin.iOS 22 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 の相互運用に踏みこんだ ことで、大きなメリットとデ メリットを抱えた。
  23. 23. ネイティブとマネージド 23 Objective-C Runtime iOS APIs Mono Runtime C# / .NET APIs Exported members Call ネイティブ iOSのプラット フォームで ネイティブに 実行されるコード マネージド mono ランタイム によって 実行が管理 されるコード Binding
  24. 24. Wrapper type と User type Xamarin.iOS における GC の挙動を考える上で、 2つの型の区別はとても重要になります。 24 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
  25. 25. Wrapper type とその寿命 マネージドの世界では、ネイティブ オブジェクトのインスタンスへのハ ンドルだけを持つ。 Wrapper type のインスタンスの寿命 は、ネイティブオブジェクトの寿命 とは無関係である。 Wrapper type のインスタンスは、マ ネージドな世界にネイティブオブ ジェクトへのハンドル以外には何も 保持していないので、GCの判断に よっていつでも自由に解放できる。 もし、後で再びそのオブジェクトが 必要になった場合には Wrapper type のインスタンスが再度作成される。 25 Managed Native UIButton UIButtonhandle 何もない Managed 側でしか 保持していないステートは無い Managed 側で不要になれば いつでも破棄できる UIView や UIButton のような Objective-C の型をラップしたもの 参照カウンター メモリーリークしない!
  26. 26. User type とその寿命 ネイティブオブジェクトのインスタ ンスへのハンドルの他に、マネージ ドな世界だけで管理されている、何 らかの“ステート”を持つ。 User type のオブジェクトは マネー ジドな世界にユーザーが定義したス テートを含む可能性があるため GC の判断によって自由に解放すること はできず、必要な間はずっと生かし 続けることを保証する必要がある。 よって、マネージオブジェクトは強 力な GCHandle によって保持される。 26 Managed Native MyButton UIButton handle field property Managed側でしか 保持していないステート Managed側のステートを保ち 続けるためにインスタンスを 破棄できない UIView や UIButton のような Wrapper type を継承し派生した型で Objective-C に対応する型が無いもの 参照カウンター いつ参照が解放されるかがポイント event
  27. 27. User type でマネージ側の参照が解放 される状況 27 解放される状況は3つ ① 他からの参照がない状況で Dispose が明示的に呼び出されたとき ② GC がオブジェクトを解放したとき ③ オブジェクトの Handle プロパティが変更されたとき ただし注意事項あり ネイティブオブジェクトの参照カウンタが1でその参照が対応するマネージオブジェクトによる参照 である場合にのみ発生します。(それ以外の時は、マネージオブジェクトに強力な GCHandle があ るため、GC の解放処理はブロックされます。) これはよくわからない(Handleの変更などしないので…)
  28. 28. そのマネージオブジェクトを保持している他のマネージオ ブジェクトが何もないときに、ユーザーが明示的に Dispose をコールすると、マネージオブジェクトの参照が 外れ、ネイティブオブジェクトとマネージオブジェクトの 間のリンクが切断されて、GC がそのオブジェクトを回収で きるようになります。 この時、ユーザーが Dispose をコールした後、ネイティブ コードが何らかの理由でそのオブジェクトを使用しようと すると、Xamarin.iOS は対応するマネージオブジェクトが 存在しないことを検出し、それを(再)作成しようとしま す。ですがこれは失敗し、プロセスを終了させる例外がス ローされてしまいます。 28 ① 他からの参照がない状況で Dispose が明示的に呼び出されたとき
  29. 29. その問題を解決するため、ユーザーが Dispose を呼び出した とき、Xamarin.iOS は以下のような挙動をします。 マネージオブジェクトの参照を外す。(まだGCに回収はされてい ない) ただし、ネイティブオブジェクトの参照カウンタが0に達するま で、ネイティブオブジェクトとマネージオブジェクトの間のリン クは切断しない。(よってGCは回収できない) これにより、ネイティブオブジェクトが存続している限り、 マネージオブジェクトを引き続き参照することができるよう にしています。 29 ① 他からの参照がない状況で Dispose が明示的に呼び出されたとき
  30. 30. ネイティブオブジェクトの参照カウンタが1に達すると、マ ネージオブジェクトからの参照が唯一の参照であり、ネイ ティブコードは当該オブジェクトを再び使用しないと安全 に仮定できます。よって、ネイティブオブジェクトとマ ネージオブジェクトの間のリンクを解除し、ネイティブオ ブジェクトを解放して、GC がマネージオブジェクトを回収 できるようにします。 30 ② GC がオブジェクトを解放したとき
  31. 31. これマジでわかりません。知ってる方いたら教えて! 31 ③ オブジェクトの Handle プロパティが変更されたとき
  32. 32. メモリーリークする仕組み 32 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 の 参照を持つ 参照
  33. 33. メモリーリークする仕組み 33 UIButton UIView Controller 参照 参照 ① 他からの参照がない状況で Dispose が明示的に呼び出されたとき ② GC がオブジェクトを解放したとき ③ オブジェクトの Handle プロパティが変更されたとき ただし注意事項あり ネイティブオブジェクトの参照カウンタが1でその参照が対応するマネージオブジェク トによる参照である場合にのみ発生します。(それ以外の時は、マネージオブジェクト に強力な GCHandle があるため、GC の解放処理はブロックされます。) これはよくわからない(Handleの変更などしないので…) ❌ 他からの参照がある ❌ 他からの参照がある ❌ Handle変更なし
  34. 34. User type の挙動についてのその他情報 User types のオブジェクトは、次のいずれかの条件が発生す ると、ネイティブ <-> マネージオブジェクトのディクショ ナリーからオブジェクトが削除されます。 ネイティブオブジェクトが dealloc されたとき(参照カウ ンタが0になったため)。 マネージオブジェクトの Handle プロパティが変更された とき(変更前の Handle 値とマネージオブジェクトの間の リンクがディクショナリーから削除されます)。 34
  35. 35. User type の挙動についてのその他情報 monoランタイムは、User type の場合ネイティブの世界で 2つの情報を追跡し続けています。 マネージオブジェクトへの GCHandle マネージオブジェクトの参照があるか外れているか。 monoランタイムはすでに GCHandle を保存する Objective-C ivar を持っているので、別の ivar を作成しませ ん。 monoランタイムはマネージオブジェクトの参照があるか どうかを GCHandle の1ビットを使用して保存しています (MANAGED_REF_BIT) 。 35
  36. 36. じゃあどうやったら メモリーリークしないの?? 36
  37. 37. イベントをデタッチする 37 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; }); イベントの デタッチ イベントの アタッチ
  38. 38. Objective-C の流儀に則り Selector を使う 38 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:")] これ何??
  39. 39. レジストラー 39
  40. 40. レジストラーとは イベントのハンドリングなどに Selector を使った場合、 Objective-C ランタイムから、マネージドのメソッドをコールす る必要があります。 レジストラーはマネージドのメンバーを Objective-C ランタイム に公開する仕組みです。 (.NET のイベントハンドラーを使った場合も当然裏で Selector に変換されています) 40 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); レジストラー
  41. 41. レジストラーとは XamariniOS-InternalSamples/MemoryNotLeakSample/obj/iPhone/Release/mtouch-cache/registrar.m 41 Objective-C Runtime iOS APIs Mono Runtime C# / .NET APIs Exported members Call Binding ここ! -(void) dismissViewButtonEvent:(NSObject *)p0 { static MonoMethod *managed_method = NULL; native_to_managed_trampoline_9 (self, _cmd, &managed_method, p0, 0x1B00); } コンパイル時に Objective-C の メソッドが生成されている Export menbers は、アプリごと に開発者が自身が実装するもの も含むため、事前にはわかりま せん。よって、コンパイル時に registrar.m ファイルを生成し、 アプリ起動時に必要なメンバー を Objective-C ランタイムに登録 しています。
  42. 42. 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 42 -(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# のメソッドを呼ぶ
  43. 43. ちなみに… XamariniOS-InternalSamples/MemoryNotLeakSample/obj/iPhone/Release/mtouch-cache/registrar.m 43 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 もレジストラーで コンパイル時に登録されています
  44. 44. プロトコル、デリゲート 44
  45. 45. プロトコトル XamariniOS-InternalSamples/MemoryNotLeakSample/obj/iPhone/Release/mtouch-cache/registrar.m 45 プロトコルも同様にマネージ側の 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
  46. 46. プロトコトル 46 プロトコルを利用する場合、レジストラーの仕組みを使って、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# は多重継承できないので…
  47. 47. Required と Optional 47 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#の対応 メソッドを判断する
  48. 48. プロトコルの定義 Optional の場合 48 [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# のメソッド名 プロトコル名
  49. 49. プロトコルの定義 Required の場合 49 [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# の メソッド名 プロトコル名
  50. 50. あれ?ところで、なんで、 Xamarin 使ったんだっけ?? 50
  51. 51. 最初にこんなこと言ってたよね!! 51 とにかく Objective-C が辛すぎて逃げたかった。
  52. 52. 52
  53. 53. 結局 Xamarin ってどうなの? 53
  54. 54. 自分個人として Xamarin やってよかっ たこと 54 ① 言語に関係ないプログラム言語についての理解がとても深まった ② iOS, .NET の思想の違いを理解し、いい感じに移植できるスキルを 身に付けることができた。 ③ iOSDC でトークできた!! とんでもなく大変な目に会って、絶対に Swift を学んだほうが早かったが、開 発者としてのスキルはレベルアップできた。楽しかったのでOK。 希少なスキルのため、指名でお仕事をすることもでき た。これも楽しかったのでOK。 身に付けた知見を発表する場を頂けて、とても楽 しかったのでOK。
  55. 55. ここがすごいよ Xamarin 55 ① ネイティブの API を100% 叩けてDRY(Don’t Repeat Your Self)が実現できるクロスプラットフォーム開発インフラである。 ② C#で構築済みのビジネスロジックがある場合、ほとんど手直しな しでそのまま使え、スキル保持者も少ないので「利益」を確保できる。 ③ Visual Studio(IDE), Azure(Cloud), Azure DevOps(CI/CD) などのエコ システムが整っている。特に Azure DevOps は Swift, Objective-C でも 普通に使えとても便利。 ④ すべての地雷を理解して回避し、使いこなせるようになった時の満 足感が半端ない!
  56. 56. Xamarin どう使ったらいいの? 56 ② 選択肢の一つとして、適材適所で使い分けするのが良い。 ③ Xamarin に限らず、Objective-C, Swift, Flutter, React Native, Kotlin / Native などできるだけ多くを体験したい。 ④ 多くの選択肢が用意されているのはありがたい。 例えば、Microsoft だからと言って Xamarin を使うわけではなく、彼ら自身も使 い分けている。iOS, Android の Word や Excel などの Office 365 アプリの UI は React Native で開発されている。 別の視点からプラットフォームを見る ことで、見識が深まります。 言語で決めず、何がしたいかで決めれるようになりたいな。 ① 「楽をするため」のツールではない!!あくまでも、効果も問題点 もすべてを理解して使うプロ向けのツール。 楽をしようとして使うと、大変な目 に会う。
  57. 57. 一番大事なのは 57 自分が楽しむこと。
  58. 58. 58 楽しいエンジニアライフを。
  59. 59. 最後にちょっとだけ宣伝 59
  60. 60. 60
  61. 61. Visual Studio for Mac で書けるプログラム 61 Mac IoT Web REST API サーバーレス ゲーム 教育用ロボット ❌
  62. 62. ご静聴ありがとうございました。

×