Open40

PHPプログラマによるはじめてのC# メモ

oracle02koracle02k

家の人に在庫管理アプリが欲しいと言われたので作ってみることに
ついでに自分の周りでC#需要が高まっているので
xamarin.iosを使っていく(mauiはまだよくわからんし)

技術的な正確性は置いておいて、体験ベースでどかどか書いていく

oracle02koracle02k

やること

  • iOSアプリの基礎を適当に覚える
  • iOSのUIを適当に覚える
  • データベースを雑に決める
  • テストコードの書き方を適当に覚える
oracle02koracle02k

データベースはなんとなくLiteDBを使うことに

  • https://github.com/mbdavid/LiteDB
  • 多分そんなにデータ量増えない
  • 同時書き込みとかない
  • トランザクションとかなくていい
  • ローカルストレージで良い
  • スキーマ作るのめんどい
  • NoSqlもたまにやってみたい
oracle02koracle02k

LiteDB

  • 基本的な使い方は公式でなんとなく理解
  • https://github.com/mbdavid/LiteDB/wiki
  • [BsonRef]属性がうまくいかない
  • とりあえず以下の形でコレクション参照を作ることに
 mapper.Entity<Stock>().DbRef(x => x.Tags, StockTagMap.TableName);

上のドキュメント見たらv4限定ってかいてあるけど[BsonRef]もv4限定なのか?

oracle02koracle02k

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()がないとコレクション参照が取得されない

oracle02koracle02k

テストについてかんがえる

  • NUnitとxUnitあたりが良いらしい
  • モックも考えたい
  • ついでにDIも考えたい
oracle02koracle02k

単純なアサーション

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());
}
oracle02koracle02k

データの永続化

  • リポジトリパターンを採用
  • LiteDBがデータマッパーなので相性も良さそう
  • 他のLiteDB利用している人ってどう扱ってるんだろ?
oracle02koracle02k

モック

ちょっと長いけど設定と検証

[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());
}
oracle02koracle02k

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>();
oracle02koracle02k

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で実際に叩く
oracle02koracle02k

iOSのUI

xib

  • XML Interface Builder
  • GUIベースレイアウト
  • 画面遷移は内包しない
  • ほぼほぼストーリーボードに置き換わっている
  • 再利用パーツを作る場合には無しではないかも?
  • https://dev.classmethod.jp/articles/xib/

ストーリーボード

  • GUIベースレイアウト
  • 画面遷移も定義できる
  • AutoLayoutで複数サイズ対応が簡単?らしい
  • 癖がある

UIKit直呼び

SwiftUI

  • 新しいやつ
  • 今回は関係なさそう
oracle02koracle02k

ライフサイクルも調べる

oracle02koracle02k

ドキュメントより

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.

oracle02koracle02k

同じくドキュメントより

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以降でもシーンサポートを切ることはできるのかな?

oracle02koracle02k

シーンベースライフサイクル

  • 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
oracle02koracle02k

あぁ、シーンベースだからUnattachedなのか、iOS12以前だとNotRunningがそれに該当するみたい

oracle02koracle02k

アプリ起動時は非アクティブ状態で起動するその後フォアグランドに移る

For apps that support scenes—The sceneWillEnterForeground(_:) method of the appropriate scene delegate object.

For all other apps—The applicationWillEnterForeground(_:) method.

https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_foreground

バックグラウンド復帰時のリソースロードなどをここで行う

oracle02koracle02k

基本的なクラス

  • UIApplication
    • アプリに必ず一つプロセスと紐づく
    • UIApplicationMain
      • アプリ起動時に呼び出される
  • UIControll
    • コントロール系のベースクラス
  • UIWindow
    • ウィンドウ描画
    • メインウィンドウ
  • UIView
    • コンテンツ管理
    • 表示物の基底クラス
  • UIResponder
    • イベントハンドリング用の抽象クラス
  • UIApplicationDelegate
    • プロセス内の重要なイベント管理

UIKitはUIApplicationとデリゲートを自動で作成する
その後メインのイベントループを開始する

oracle02koracle02k
Put your app's launch-time initialization code in one or both of the following methods:
application(_:willFinishLaunchingWithOptions:)
application(_:didFinishLaunchingWithOptions:)
oracle02koracle02k

ライフサイクルは
UIApplicationとデリゲートを押さえておけばよさそうかな

oracle02koracle02k

xamarin.ios

  • C#側からiOS側の呼び出しはP/Invokeでよさそうか?
  • C#側をselectorなどでiOS側に渡すには
    • Export[]でC#側をobjc側に公開
      • コンパイル時にregistrar.mが生成
      • アプリ起動時にobjcのランタイムに登録
      • objc > c(trampoline)で引数をマネージド変換 > mono_runtime_invoke経由で呼び出し

この辺は興味あるけど突っ込むと戻って来れなくなりそうなので
今は使い方だけおさえよう
とりあえずregistrar.mが生成されてることは確認

oracle02koracle02k

StoryBoardの把握

  • xcodeでめちゃめちゃトライする
  • xamarin.ios経由でも大丈夫っちゃ大丈夫
  • が,たぶんswiftでガチャガチャテストした方が良さそう
  • xamarin側とsyncした時にコード生成がうまくいかないことがある
oracle02koracle02k

AutoLayoutやらの学びも必要
わかっちゃいたいけど、学ぶものがありすぎる
いうてswiftも知ってる分けじゃないから学ぶ量的にはそんなに変わってないか?

oracle02koracle02k

UIKit周りの扱い

  • objective-cとC#で言語機能が違う
  • 言語機能の違いによる歪みが出る
  • objective-cのprotocolはオプショナルな実装をもてる
  • C#のインターフェイスではそうはいかない
    • オプショナルな実装を持っているものはabstract + 実行時例外で実装されている
  • protocol(objective-c)は多重継承可能なのにたいし、abstract(C#)は許可されていない
  • オプショナルな実装を持つprotocolを多重継承して作る形をC#側で再現するのが難しい
  • C#8.0からインターフェイスのデフォルト実装というものが入ったらしい
    • 今だとこの辺りを利用して回避できるのだろうか?
oracle02koracle02k

どうでもいい話

rider使おうと思ったけど、プロジェクトテンプレートが出てこない・・・なぜだ

oracle02koracle02k

Rider最新にしたらプロジェクトテンプレート出てくれたけど
今度はフォルダ整理をどうしたらいいのやら
storyboardの編集などはxcode向けのプロジェクトが作られて
xcodeで操作するようになるけど、生成されたxcodeのプロジェクトと
C#側のディレクトリ階層が違っていることがある
さらにxcode側で変更かけるとc#側に同期される際に
xcodeのフォルダ階層でファイルができる

oracle02koracle02k

レイアウトに苦戦

  • オートレイアウトの扱い
  • わけわからんプロパティ
  • ナビゲーションバーへの潜り込み

レイアウト周りの理解が一番大変かもしれない・・・
もはやxamarin関係ない。

oracle02koracle02k
  • Segueの種類と意味
  • ストーリーボードからのアクション
  • 改めてライフサイクル