PHPプログラマによるはじめてのC# メモ
家の人に在庫管理アプリが欲しいと言われたので作ってみることに
ついでに自分の周りでC#需要が高まっているので
xamarin.iosを使っていく(mauiはまだよくわからんし)
技術的な正確性は置いておいて、体験ベースでどかどか書いていく
やること
- iOSアプリの基礎を適当に覚える
- iOSのUIを適当に覚える
- データベースを雑に決める
- テストコードの書き方を適当に覚える
データベースはなんとなくLiteDBを使うことに
- https://github.com/mbdavid/LiteDB
- 多分そんなにデータ量増えない
- 同時書き込みとかない
- トランザクションとかなくていい
- ローカルストレージで良い
- スキーマ作るのめんどい
- NoSqlもたまにやってみたい
LiteDB
- 基本的な使い方は公式でなんとなく理解
- https://github.com/mbdavid/LiteDB/wiki
- が
[BsonRef]
属性がうまくいかない - とりあえず以下の形でコレクション参照を作ることに
mapper.Entity<Stock>().DbRef(x => x.Tags, StockTagMap.TableName);
上のドキュメント見たらv4限定ってかいてあるけど[BsonRef]もv4限定なのか?
公式ページにv5のドキュメントあった
LiteDBのコレクション参照
こういったクラスがあったとして
public class StockTag
{
public int Id { get; set; }
public string Name { get; set; }
public List<StockTag> Children { get; set; }
}
コレクション参照をこう書き
public void Register(BsonMapper mapper)
{
mapper.Entity<StockTag>().DbRef(x => x.Children, TableName);
}
取得するときはこう
public IEnumerable<StockTag> FetchAll()
{
return db.GetCollection<StockTag>(TableName).Include(x => x.Children).FindAll();
}
Include()がないとコレクション参照が取得されない
テストについてかんがえる
- NUnitとxUnitあたりが良いらしい
- モックも考えたい
- ついでにDIも考えたい
単純なアサーション
public void test()
{
var stock = new Stock
{
Name = "材料1",
Price = 100,
Quantity = 3,
Tags = new List<StockTag> {new StockTag {Name = "tag1"}},
};
Assert.AreEqual("Stock(Id=0, Name=材料1, Price=100, Quantity=3, Tags=[tag1])", stock.ToString());
}
データの永続化
- リポジトリパターンを採用
- LiteDBがデータマッパーなので相性も良さそう
- 他のLiteDB利用している人ってどう扱ってるんだろ?
モック
ちょっと長いけど設定と検証
[Test]
public void MockTest()
{
var collection = new Mock<ILiteCollection<Stock>>();
var db = new Mock<ILiteDatabase>();
var map = new Mock<IEntityMap<Stock>>();
var result = new Mock<IEnumerable<Stock>>();
// 入出力セット
map.Setup(x => x.Collection(db.Object))
.Returns(collection.Object);
collection.Setup(x => x.Include(x => x.Tags))
.Returns(collection.Object);
collection.Setup(x => x.FindAll())
.Returns(result.Object);
// 実行
var repository = new StockRepository(db.Object, map.Object);
repository.FetchAll();
// 検証
map.Verify(x => x.Collection(db.Object), Times.AtLeastOnce());
collection.Verify(x => x.FindAll(), Times.AtLeastOnce());
collection.Verify(x => x.Include(x => x.Tags), Times.AtLeastOnce());
}
LiteDatabaseの破棄まわり
- https://github.com/mbdavid/LiteDB/blob/master/LiteDB/Client/Database/LiteDatabase.cs
- IDisposableを実装
- disposeパターン(C#だと割と一般的?なのかな)
- Disposeの処理とファイナライザ(デストラクタ)でアンマネージドなリソースを破棄
- 多重解放にならないようフラグで制御
- usingはIDisposableを実装したオブジェクトを要求
- usingを抜ける際にDisposeが呼ばれる
DIコンテナ
- Microsoft.Extensions.DependencyInjection
- ServiceCollectionのインスタンス作って、そこにどかどか登録していく
- providerからインスタンスの取得/生成
- 型パラメータで登録する方法とインスタンス生成を登録する方法
- 依存先の自動解決確認はこれから
var services = new ServiceCollection();
services.AddSingleton<StockTagMap>();
services.AddSingleton(x => new StockTagRepository(db, x.GetRequiredService<StockTagMap>()));
services.AddSingleton<StockMap>();
services.AddSingleton(x => new StockRepository(db, x.GetRequiredService<StockMap>()));
var provider = services.BuildServiceProvider();
var tagRepository = provider.GetRequiredService<StockTagRepository>();
var stockRepository = provider.GetRequiredService<StockRepository>();
iOS側
そろそろxamarin.iosの門を叩く必要がありそう。
xamarinの後発としてmauiなんてのが出てきているらしいです。
- https://docs.microsoft.com/en-us/dotnet/maui/
- https://github.com/dotnet/maui
- まだプレビュー段階らしい
- いうて、iOSとの繋ぎはiOS側の制約をもろに受けると思うので、xamarinと対して変わらんのじゃないかな?
- ようわからんけども
- iOSは動的なコード実行を許してないのですくなくともAOTコンパイルが必要だとは思う
- xamarinとmauiの違いについては後で調べよう
やること
- UIどうやって作るねん。
- そもそもiOSのUIKitよく知らん
- ストーリーボードとかもよく知らん
- のでiOS側の調査をする
- その後、xamarin.iosで実際に叩く
iOSのUI
- いくつかある
- xib
- storyboard
- UIKit直呼び
- SwiftUI
-
https://qiita.com/st43/items/b21d61614ea8932b227f
- ↑の記事が非常によくまとまっています。
- 以下は、ほぼこの記事からの抜粋した個人メモ
xib
- XML Interface Builder
- GUIベースレイアウト
- 画面遷移は内包しない
- ほぼほぼストーリーボードに置き換わっている
- 再利用パーツを作る場合には無しではないかも?
- https://dev.classmethod.jp/articles/xib/
ストーリーボード
- GUIベースレイアウト
- 画面遷移も定義できる
- AutoLayoutで複数サイズ対応が簡単?らしい
- 癖がある
UIKit直呼び
- https://qiita.com/nagisawks/items/222c881d6798c46a390f
- 見た目の判断が実行しないとわからないのが辛そう
- 構成を理解するには一番いいかも
SwiftUI
- 新しいやつ
- 今回は関係なさそう
UIKitの動作について
- 理解するためにコードで制御してみる
-
https://zenn.dev/kazumalab/articles/76ca59f82c189cf33d43
- また非常にためになる記事です
ライフサイクルも調べる
ドキュメントより
In iOS 13 and later, use UISceneDelegate objects to respond to life-cycle events in a scene-based app.
In iOS 12 and earlier, use the UIApplicationDelegate object to respond to life-cycle events.
同じくドキュメントより
Scene support is an opt-in feature. To enable basic support, add the UIApplicationSceneManifest key to your app’s Info.
plist file as described in Specifying the Scenes Your App Supports.
ってことはiOS13以降でもシーンサポートを切ることはできるのかな?
シーンベースライフサイクル
- iOS13以降
各ステータス
- Foreground Active: フォアグランドで実行中
- Foregrround Inactive: フォアグランドで実行中 (遷移中の短い間とか?)
- Background: バックグラウンドで実行中
- Suspend: バックグラウンドで待機中?
- Unattached: 停止中?
ステータス遷移
- Foreground Active
- to: Foreground Inactive
- Foreground Inactive
- to: Background
- Background
- to: Suspend
- to: Unattached
- Unattached
- to: Foreground Inactive
- to: Background
あぁ、シーンベースだからUnattachedなのか、iOS12以前だとNotRunningがそれに該当するみたい
アプリ起動時は非アクティブ状態で起動するその後フォアグランドに移る
For apps that support scenes—The sceneWillEnterForeground(_:) method of the appropriate scene delegate object.
For all other apps—The applicationWillEnterForeground(_:) method.
バックグラウンド復帰時のリソースロードなどをここで行う
基本的なクラス
- UIApplication
- アプリに必ず一つプロセスと紐づく
- UIApplicationMain
- アプリ起動時に呼び出される
- UIControll
- コントロール系のベースクラス
- UIWindow
- ウィンドウ描画
- メインウィンドウ
- UIView
- コンテンツ管理
- 表示物の基底クラス
- UIResponder
- イベントハンドリング用の抽象クラス
- UIApplicationDelegate
- プロセス内の重要なイベント管理
UIKitはUIApplicationとデリゲートを自動で作成する
その後メインのイベントループを開始する
Put your app's launch-time initialization code in one or both of the following methods:
application(_:willFinishLaunchingWithOptions:)
application(_:didFinishLaunchingWithOptions:)
ライフサイクルは
UIApplicationとデリゲートを押さえておけばよさそうかな
xamarin
やっとxamarinかな。。。多分
monoランタイムとILコードを合わせてビルドしたバイナリをobjective-c経由で読むとかそんな感じだろうか?
また良さそうな記事(スライド)を拝借
xamarin.ios
- C#側からiOS側の呼び出しはP/Invokeでよさそうか?
- C#側をselectorなどでiOS側に渡すには
- Export[]でC#側をobjc側に公開
- コンパイル時にregistrar.mが生成
- アプリ起動時にobjcのランタイムに登録
- objc > c(trampoline)で引数をマネージド変換 > mono_runtime_invoke経由で呼び出し
- Export[]でC#側をobjc側に公開
この辺は興味あるけど突っ込むと戻って来れなくなりそうなので
今は使い方だけおさえよう
とりあえずregistrar.mが生成されてることは確認
StoryBoardの把握
- xcodeでめちゃめちゃトライする
- xamarin.ios経由でも大丈夫っちゃ大丈夫
- が,たぶんswiftでガチャガチャテストした方が良さそう
- xamarin側とsyncした時にコード生成がうまくいかないことがある
AutoLayoutやらの学びも必要
わかっちゃいたいけど、学ぶものがありすぎる
いうてswiftも知ってる分けじゃないから学ぶ量的にはそんなに変わってないか?
UIKit周りの扱い
- objective-cとC#で言語機能が違う
- 言語機能の違いによる歪みが出る
- objective-cのprotocolはオプショナルな実装をもてる
- C#のインターフェイスではそうはいかない
- オプショナルな実装を持っているものはabstract + 実行時例外で実装されている
- protocol(objective-c)は多重継承可能なのにたいし、abstract(C#)は許可されていない
- オプショナルな実装を持つprotocolを多重継承して作る形をC#側で再現するのが難しい
- C#8.0からインターフェイスのデフォルト実装というものが入ったらしい
- 今だとこの辺りを利用して回避できるのだろうか?
どうでもいい話
rider使おうと思ったけど、プロジェクトテンプレートが出てこない・・・なぜだ
レイアウトに苦戦
- オートレイアウトの扱い
- わけわからんプロパティ
- ナビゲーションバーへの潜り込み
レイアウト周りの理解が一番大変かもしれない・・・
もはやxamarin関係ない。
- Segueの種類と意味
- ストーリーボードからのアクション
- 改めてライフサイクル
レイアウトやらUI関係で参考にさせてもらった記事など
- https://qiita.com/orimomo/items/e12a0e468f083bcb7a50
- https://hari-blog.com/uitableviewcell-subclass
- https://hari-blog.com/tag
- https://qiita.com/motamura/items/7e061a52be33a39267b7
- https://qiita.com/fummicc1_dev/items/a30e3cbfbf1148b0ec84
- https://hajihaji-lemon.com/swift/segue_presentation/
- https://qiita.com/kinopontas/items/d08f84dbb711c5acbe28
- https://qiita.com/shotarohorai/items/ac54f5c0e7145efa6abc
- https://qiita.com/yimajo/items/254c7cebab7864678246