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

Weitere ähnliche Inhalte

Mehr von Tomohiro Suzuki

Xamarin.iOS 開発者から見た Swift
Xamarin.iOS 開発者から見た SwiftXamarin.iOS 開発者から見た Swift
Xamarin.iOS 開発者から見た SwiftTomohiro Suzuki
 
JXUG Xamarin.iOS & Xamarin.Android App Center ハンズオン
JXUG Xamarin.iOS & Xamarin.Android App Center ハンズオンJXUG Xamarin.iOS & Xamarin.Android App Center ハンズオン
JXUG Xamarin.iOS & Xamarin.Android App Center ハンズオンTomohiro Suzuki
 
MacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイント
MacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイントMacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイント
MacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイントTomohiro Suzuki
 
Cocos sharpformsの使用例
Cocos sharpformsの使用例Cocos sharpformsの使用例
Cocos sharpformsの使用例Tomohiro Suzuki
 
がんばれガンプ ソルバルウを倒せについて
がんばれガンプ ソルバルウを倒せについてがんばれガンプ ソルバルウを倒せについて
がんばれガンプ ソルバルウを倒せについてTomohiro Suzuki
 
Xamarin Native vs Xamarin Forms
Xamarin Native vs Xamarin FormsXamarin Native vs Xamarin Forms
Xamarin Native vs Xamarin FormsTomohiro Suzuki
 
がんばれガンプ ソルバルウを倒せ
がんばれガンプ ソルバルウを倒せがんばれガンプ ソルバルウを倒せ
がんばれガンプ ソルバルウを倒せTomohiro Suzuki
 
Xamarin 実戦投入時に気をつけたいことあれこれ
Xamarin 実戦投入時に気をつけたいことあれこれXamarin 実戦投入時に気をつけたいことあれこれ
Xamarin 実戦投入時に気をつけたいことあれこれTomohiro Suzuki
 
Xamarin 実戦投入時の留意点再確認
Xamarin 実戦投入時の留意点再確認Xamarin 実戦投入時の留意点再確認
Xamarin 実戦投入時の留意点再確認Tomohiro Suzuki
 
Cocos sharpでゲーム開発してみました
Cocos sharpでゲーム開発してみましたCocos sharpでゲーム開発してみました
Cocos sharpでゲーム開発してみましたTomohiro Suzuki
 

Mehr von Tomohiro Suzuki (10)

Xamarin.iOS 開発者から見た Swift
Xamarin.iOS 開発者から見た SwiftXamarin.iOS 開発者から見た Swift
Xamarin.iOS 開発者から見た Swift
 
JXUG Xamarin.iOS & Xamarin.Android App Center ハンズオン
JXUG Xamarin.iOS & Xamarin.Android App Center ハンズオンJXUG Xamarin.iOS & Xamarin.Android App Center ハンズオン
JXUG Xamarin.iOS & Xamarin.Android App Center ハンズオン
 
MacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイント
MacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイントMacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイント
MacでiOSアプリの実機ビルドと実機テストを設定する際のつまづきポイント
 
Cocos sharpformsの使用例
Cocos sharpformsの使用例Cocos sharpformsの使用例
Cocos sharpformsの使用例
 
がんばれガンプ ソルバルウを倒せについて
がんばれガンプ ソルバルウを倒せについてがんばれガンプ ソルバルウを倒せについて
がんばれガンプ ソルバルウを倒せについて
 
Xamarin Native vs Xamarin Forms
Xamarin Native vs Xamarin FormsXamarin Native vs Xamarin Forms
Xamarin Native vs Xamarin Forms
 
がんばれガンプ ソルバルウを倒せ
がんばれガンプ ソルバルウを倒せがんばれガンプ ソルバルウを倒せ
がんばれガンプ ソルバルウを倒せ
 
Xamarin 実戦投入時に気をつけたいことあれこれ
Xamarin 実戦投入時に気をつけたいことあれこれXamarin 実戦投入時に気をつけたいことあれこれ
Xamarin 実戦投入時に気をつけたいことあれこれ
 
Xamarin 実戦投入時の留意点再確認
Xamarin 実戦投入時の留意点再確認Xamarin 実戦投入時の留意点再確認
Xamarin 実戦投入時の留意点再確認
 
Cocos sharpでゲーム開発してみました
Cocos sharpでゲーム開発してみましたCocos sharpでゲーム開発してみました
Cocos sharpでゲーム開発してみました
 

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