かぐや様は告らせたい(17)

会長とかぐやはついに交際開始したわけだが、 2人が交際していることは周囲には秘密みたいだ。 確かに、四宮本家が知ったら、2人を別れさせようとするに決まっているか。

それでも、幸せのおすそ分けをしたいかぐや様のお可愛いこと。 唯一自分から話した柏木神は、もはや親友的なポジションにいると言っても過言じゃない。 かぐやに性教育ができるのは彼女だけだし。 藤原書記はそのポジションを奪われたな。

その藤原書記は株の下落が止まらない。 ハゲヅラをかぶるヒロインがどこにいる。 一応こんなんでも連載当初はヒロイン扱いだったはずなんだけど…。 藤原ママの頃は輝いていた。

むしろ、かぐやと眞妃が急接近。 四宮家と四条家の対立は激しさを増し、経済戦争勃発かという状況で、 それでも歩み寄れたのは眞妃の性格の良さが大きいだろうな。 かぐやのために男子どもに釘を刺しに行くところなんか、 もろに性格の良さが現れている。 ホント、報われないのが不憫でならない。 そんな歩み寄った2人が一緒に見たのが、 柏木神から渡された性教育 DVD というオチは見事。

五等分の花嫁(13)

学園祭編もクライマックス。 学園祭終了後、風太郎がついに五つ子の中から1人を選ぶことに。 本命・三玖、対抗・ニ乃、大穴・四葉と予想していたが、 風太郎が選んだのが四葉とはね。 三玖は1巻からおそらくずっと風太郎を想っていただけに、 選ばれないのは残酷だ。 一方で、四葉は三玖以上にずっと長く風太郎を想っていたわけなので、 彼女の想いがスルーされるのもあんまりか。 考えてみると、過去から未来まで綺麗に繋がった感がする。 最初から花嫁は四葉と、作者のプロットでは決まってたのかもね。

あと、13巻では五つ子の実の父親が出てきたが、 本作では稀に見るクソ野郎だったな。 別れて正解だったと思うくらいに。 悪人らしい悪人が出てこないマンガなので、そのクソさが一層際立っている。 接し方は不器用でも、 マルオの方がよっぽど父親らしい。

次の14巻で五等分の花嫁はついに完結。 四葉は他の姉妹に遠慮しているところがあるし、 周りもすんなりおめでとうとは行かなさそう。 四葉が他の姉妹に対してどう落とし前をつけていくのか。 最後まで目が離せない。 表紙の花嫁は四葉で確定、だよな。きっと。

五等分の花嫁(13) (週刊少年マガジンコミックス)

五等分の花嫁(13) (週刊少年マガジンコミックス)

からかい上手の(元)高木さん(8)

中学生のちぃが登場する話が 2 話収録されていて、 ちぃが中学生になっても変わらず西片くんと(元)高木さんの仲が良いのは良きかな。 バレンタインで高木さんが西片くんにチョコを渡すのは微笑ましかったけど、 ちぃが隣席の男子(名前なんだっけ?)に渡す、 かもしれないのをあまり微笑ましく感じないのは、 自分の子供もいつか誰かにチョコを渡すかもしれないことを想起させるからだろうな。 西片くんの反応が見てみたいところだ。

ReduxSharp – 単方向データフローを C# で実践するためのライブラリ

はじめに

IssueHub で使っている自作のライブラリ ReduxSharp。

github.com

www.nuget.org

すでに GitHubソースコード、NuGet でパッケージを公開していたんだけど、 紹介記事を書いていなかった。 IssueHub のソースコード公開を機に紹介したいと思う。

ReduxSharp について

JavaScript には Redux というライブラリがある。 React で Single Page Application を開発した事がある人は使ったことがあるんじゃないだろうか。

redux.js.org

Redux がどういうものかは公式のドキュメントを読んでもらうとして、 ReduxSharp は名前の通り、その C# 版。

Redux の .NET 移植版としては先に Redux.NET というのが存在していた。 GitHub のスター数も Redux.NET の方が圧倒的に多い。

github.com

モチベーション

Redux.NET が既に存在するのに、それでも ReduxSharp を作ったのは、 Redux.NET に不満があったから。

どんな不満かというと、まずは Redux.NET が Rx.NET に依存していること。 内部で ReplaySubject<T> 使っちゃっている。 ライブラリ自体は小さいのに、Rx.NET という大きいライブラリに依存していて、 インストールするともれなく付いてくるのが嫌だった。 たとえ一緒に使うことが多いとしても、ね。

もう 1 つが、アクションを発行する Dispatch メソッドの引数が object なところ。 頻繁に発行されるアクションは構造体で定義したいのに、これだとボックス化してしまう。

以上の 2 点が気に入らなかった。

ReduxSharp の使い方

  1. NuGet から ReduxSharp のパッケージをインストールする
    • Install-Package ReduxSharp
  2. アプリのステートを表すクラスを定義
  3. アクションを定義
  4. Reducer を定義
    • IReducer<TState> インタフェースを実装する
  5. Store<TState>インスタンスを作成する
    • Reducer のインスタンスを渡す必要がある
    • ステートの初期値を渡すことができる
  6. 変更通知を受け取りたいクラスに IObserver<T> を実装する
  7. Store<TState>.Subscribe(IObserver<TState>) で変更通知を受け取るインスタンスを登録する
  8. 任意のアクションをストアに Dispatch する

サンプルコードは次の通り。

using System;
using ReduxSharp;

// 6. 変更通知を受け取りたいクラスに IObserver<T> を実装する
class Program : IObserver<AppState>
{
    static void Main(string[] args)
    {
        // 5. Store<TState> のインスタンスを作成する
        var store = new Store<AppState>(
            new AppReducer(),
            new AppState(0));

        // 7. Store<TState>.Subscribe(IObserver<TState>) で変更通知を受け取るインスタンスを登録する
        var p = new Program();
        using (store.Subscribe(p))
        {
            // 8. 任意のアクションをストアに Dispatch
            store.Dispatch(new Increment());
            store.Dispatch(new Increment());
            store.Dispatch(new Decrement());
            store.Dispatch(new Increment());
        }

        Console.ReadLine();
    }

    public void OnNext(AppState value) =>
        Console.WriteLine(value.Count);

    public void OnCompleted() { }

    public void OnError(Exception error) { }
}

// 2. アプリのステートを表すクラスを定義
public class AppState
{
    public AppState(int count) => Count = count;

    public int Count { get; }
}

// 3. アクションを定義
public readonly struct Increment { }

public readonly struct Decrement { }

// 4. Reducer を定義
public class AppReducer : IReducer<AppState>
{
    public AppState Invoke<TAction>(AppState state, TAction action)
    {
        switch (action)
        {
            case Increment _:
                return new AppState(state.Count + 1);
            case Decrement _:
                return new AppState(state.Count - 1);
            default:
                return state;
        }
    }
}

ミドルウェア

ReduxSharp でもミドルウェアをサポートしている。 IMiddleware<TState> インタフェース実装したクラスのインスタンスを、 Store<TState> のコンストラクタで渡せば使える。

using Newtonsoft.Json;
using ReduxSharp;

public class ConsoleLoggingMiddleware<TState> : IMiddleware<TState>
{
    public void Invoke<TAction>(IStore<TState> store, IDispatcher next, TAction action)
    {
        Console.WriteLine(JsonConvert.SerializeObject(store.State));

        next.Invoke(action);

        Console.WriteLine(JsonConvert.SerializeObject(store.State));
    }
}

Reducer の Invoke が呼ばれる前後に任意の処理を挿入できる。 アクションとステートを記録するも良し、ロギングを挟むも良し。

非同期処理

今のところ非同期処理はサポートしていない。 非同期処理を行いたい場合、redux-thunk みたいなミドルウェアを書いてもいいが、 下記の DispatchAsync みたいな拡張メソッドを実装して、 非同期アクションを実行できるようにする方が使いやすい。 IssueHubではそれで対応している。

using System;
using System.Threading.Tasks;
using ReduxSharp;

public delegate ValueTask AsyncActionCreator<TState>(IStore<TState> store);

public static class StoreEx
{
    public static async ValueTask DispatchAsync<TState>(
        this IStore<TState> store,
        AsyncActionCreator<TState> asyncActionCreator)
    {
        if (asyncActionCreator == null) throw new ArgumentNullException(nameof(asyncActionCreator));
        await asyncActionCreator(store).ConfigureAwait(false);
    }
}

本体に組み込んでもいいけど、これくらいならまぁ別に毎回書いてもいいか、 と思って組み込んでいない。

ReduxSharp の実装について

Redux だと結構な頻度でアクションを Dispatch するので、アクションをクラスで定義すると、 頻繁にインスタンスを生成してはすぐ破棄して効率悪い。 頻繁に使うアクションは構造体にしたいところ。 また、アクションは作ったら変更がほぼ無いので readonly struct にできる。

そんな構造体で定義したアクションをボックス化せずに使えるよう、 アクションを Dispatch するための Store のメソッドはジェネリックStore.Dispatch<TAction>(TAction) にした。 それに合わせて、Reducer がアクションを処理するためのメソッドもジェネリクスIRedicer<TState>.Invoke<TAction>(TState, TAction) にした。 Reducer の中ではアクションの型をチェックして処理を分岐するが、 C# なら switch や is で型をマッチさせて分岐できるので自然に書ける。

ストア自体は IObservable<T> を実装していて、 そこは Redux.NET と一緒。 ただ、中では Rx.NET を使わず愚直に実装している。 使っていないので Rx.NET に依存していない。 .NET Standard 2.0 に対応していて、サードパーティのライブラリへの依存はない。 IObservable<T> を実装しているので、Rx.NET と組み合わせて使える。 実際、IssueHub でも組み合わせて使っている。

ベンチマーク

ベンチマークも取っていて、Redux.NETと比較して、 単純なアクションの Dispatch なら ReduxSharp の方が高速。 構造体で定義したアクションならゼロアロケーション

f:id:griefworker:20200121111612p:plain

ただ、ミドルウェアを使った場合はまだ Redux.NET の方が速いので、そこは今後の課題だ。 良いアイデアがないものか。

余談

C# には ValueTask と async/await があるので、 最初は Reducer の中で非同期処理を書けるようにしようと考えていた。 しかし、いざ実装してベンチマークをとってみたら激遅。

ReduxSharp を使うアプリでやる非同期処理というと、WebAPI 呼び出しとかファイル IO とか。 同期処理になるケースがまず無いので、ValueTaskにしたところで大した効果は無かった。 むしろ全部 ValueTask にしちゃうことで非同期でないアクションにも影響が出てしまった。

Redux.NET と比べて桁違いに遅かったので、非同期 Reducer はスパッと断念。 せめて、ミドルウェアくらいは非同期にしたかったが、構造体で定義したアクションのこと考えたら、やっぱり見送り。

おわりに

Xamarin で使うために作ったライブラリではあるけど、WPF や WinForms でも使える。 Blazor で使えるかどうかは試してないが、おそらく一手間かかりそう。 機能的には完成していて、今後やるとしたらパフォーマンスチューニングくらいだ。 特にミドルウェアを組み込んだとき Redux.NET に負けているのはなんとかしたい。

IssueHub を支える技術

はじめに

先日、GitHub Issues のクライアントアプリ『IssueHub』を OSS として公開した。

tnakamura.hatenablog.com

IssueHub でどんな技術を使ったのか、宣言通り紹介したいと思う。

採用した技術

IssueHub は Xamarin.Forms で開発した。 言語はC#。 Xamarin.Forms を使った理由は、ソースコードをできるだけ共通化して iOSAndroid の両方に対応したかったからだ。

今はメインのスマホiPhone を使っているけど、 最近の iPhone は手が届かない金額になったので、 SE2 が出なかったら Android に移行するかもしれない。 そのときに自分のアプリを Android でも使いたいので、 クロスプラットフォーム開発が可能な Xamarin.Forms を選んだ。

Xamarin.Forms を選んだ理由

クロスプラットフォーム開発が可能なものとして、 Xamarin.Forms の他に ReactNative や Flutter なんかがある。 なんで Xamarin.Forms だったのか。

最大の理由は C# で書けるため。 ネイティブの機能が必要になったときに、 ReactNative や Flutter だとプラグインをネイティブの言語で書かなければいけないが、 Xamarin だとネイティブの APIC# でラップしてあるので、 ネイティブを呼び出す部分も C# で書ける。 この辺りの Xamarin のアプローチは個人的にかなり筋がいいと思っている。 自分が一番慣れている言語で書けるのは大きい。

GitHub の Issues のクライアントアプリというのが、 要件的にちょうど良かったというのもある。

主なアーキテクチャ

Xamarin.Forms なので主なアーキテクチャは MVVM。 大きなライブラリに依存したくなかったので、 Xamarin.Forms を使った開発では定番になっている Prism.Forms を使わず、 ReactiveProperty を使っている。 モデルの部分に関しては、Redux を採用。 そのためのライブラリとして、ReduxSharp という自作のライブラリを使っている。

Redux

最初、DDD ライクなレイヤーアーキテクチャで設計していたけど、 どこに状態を持たせるのかで頭を悩ませた。 できれば1箇所にまとめたい。 Store とか導入する例もネットで見かけた。 でも Store を持つなら、それって Redux と同じなのでは。 ということで Redux にした。

ユースケースにアクションが対応していて、 アクションで Store が持つステートが変わったら変更通知が ViewModel に届き、 ViewModel が保持するデータが変わって画面が変わる。 簡単に説明すると、そんな感じの仕組みになっている。 ReduxSharp については、これまた説明すると長くなるので、別の記事を書きたい。

認証

認証では Xamarin.Auth を使っている。 GitHub の Basic 認証のエンドポイントは廃止されるので、認証はパーソナルアクセストークンか OAuth に対応する必要があった。 パーソナルアクセストークンを発行してアプリに設定してもらうのは、使い始めるまでのハードルが高いので OAuth に対応。 まぁ、結局はユーザー自身がビルドして実機デプロイするという、この上ない高さのハードルになってしまったわけだけど。

OAuth に対応する場合、WebView を使って組み込みブラウザではセキュリティ的に好ましく見られないため、 iOS では SafariViewController、Android では ChromeCustomTab を使ってくれる Xamarin.Auth を使っている。

その他に利用しているライブラリ

GitHub API のクライアントは Octokit。

github.com

Markdown の表示には MarkdownView。

github.com

純粋なクライアントアプリなので、使っているライブラリはそれくらい。

あと、GitHub の Client Id と Client Secret をソースコードに埋め込みたくなかったので、 リポジトリ管理外の secrets.json に秘密情報を書き、ビルド時に組み込む手法を導入した。

tnakamura.hatenablog.com

IssueHub の今後について

自分が使う分には欲しい機能だいたい揃っているので、 バグが見つかったら修正はするものの、 積極的な機能追加は考えてない。 マネタイズできそうにないし。

スマホAndroid に乗り換えたら Android 対応に本腰を入れると思う。

他のアプリを公開するために Apple Developer の登録料を支払ったら、 ついでに IssueHub も App Store で公開する、かもしれない。 Android の方は登録料が必要なのは初回だけなので、 スマホAndroid にしたら Play Store に公開する可能性は高い。

ぼくたちは勉強ができない(15)

祖母の真意を理解できたのと、成幸への想いを自覚してから、理珠はかなりキャラが変わったな。 見た目だけでなく言動も。 以前美容室でイメチェンしたときよりは断然良い。 思わせぶりな態度をとって成幸をからかう姿を見ると、人の気持ちが分からないと悩んでいた少女がよくぞここまで成長した、と感慨深くなる。

そして15巻でついに、受験の第1関門であるセンター試験へ。そのセンター試験でまさかの、成幸に最大のピンチが到来。 連載をリアルタイムで読んでいて、え?まじで?浪人しちゃうの?いやラブコメだし…。でも引き延ばしもありえるか。なんて考えがよぎってハラハラした。 そんな窮地の成幸を救ったのはうるか。 成幸に寄り添うその姿はまさにメインヒロインだった。

自分は真冬先生やあしゅみー先輩を気に入っているんだけど、さすがに成幸の相手に選ばれるのは三人娘の誰かなんだろうと思う。それなら、三人娘の中ではうるかを希望。5年越しの恋を実らせてほしい。

ぼくたちは勉強ができない 15 (ジャンプコミックスDIGITAL)

ぼくたちは勉強ができない 15 (ジャンプコミックスDIGITAL)

IssueHub – GitHub の Issues に特化した iOS/Android 用モバイルアプリ

IssueHub というモバイルアプリのソースコードGitHub で公開した。

github.com

IssueHub は、GitHub の Issues に特化したクライアントアプリ。 競合するのは CodeHub や GitHub 公式のモバイルアプリ(まだベータ版)辺りかな。 Issues 特化と Todo アプリっぽく操作できるのをコンセプトにし、差別化を試みている。

iOSAndroid の両方に対応。 ただし、App Store と Play Store で公開はしていない。

GitHub で無料ユーザーでもプライベートリポジトリが無制限に作れるようになってから、 Bitbucket にあった個人開発用のリポジトリを全部 GitHub に移行したけど、 GitHub のイシューを管理するちょうど良いアプリがなかった。 CodeHub は高機能過ぎてイシューを管理するだけにしてはオーバーキルなのと、 プライベートリポジトリはアプリ内課金しないと扱えなかったので、試したもののすぐに使うのを断念。 Bitbucket のときは自作の Bitissues という、これまた Bitbucket の Issues に特化したアプリを使っていて、 同じようなものが GitHub にも欲しいと思い、Bitissues 作ったときのノウハウがあるので作ることにした。 開発には Xamarin.Forms を採用。 技術的な話は長くなるので、別に記事を書こうと思う。

ソースコードを公開した理由は、GitHub 公式のモバイルアプリが出るから。 iOS 版がリリースできるくらいになって、Apple Developer の登録料を捻出するべきかどうしようかと迷っているタイミングで、 GitHub 公式モバイルアプリのニュースが飛び込んできて、そこでモチベーションが萎えてしまった。 毎年 Apple Developer の登録料を払うのは財布的に厳しいし、マネタイズもまぁ無理だろう。 この手の開発者向けアプリは、広告を貼ってもクリックされない。 GitHub のクライアントアプリなので、有料アプリとして売るのも気が引ける。 有料だったらみんな公式モバイルアプリの方を使うでしょ。 ユーザーは自分だけかもしれない。 ならば GitHubソースコードを公開して自分のポートフォリオに加えてしまえ、 という結論に至った。

ビルド手順は README に詳しく書くが、簡単に説明するなら次のようになる。

  1. GitHub の Developer settings の OAuth apps に登録し、Client ID と Client Secret を入手する。
    • リダイレクト URL は issuehub://oauth2redirect
  2. IssueHub のソースコードをクローンし、Visual Studio 2019 または Visual Studio for Mac で IssueHub.sln を開く。
  3. IssueHub.csproj があるディレクトリに下記の secrets.json を作成し、ビルドして実機にデプロイ。
{
    "GitHubClientId": "{Your GitHub Client Id HERE}",
    "GitHubClientSecret": "{Your GitHub Client Secret HERE}",
    "GitHubAuthorizationCallbackUrl": "issuehub://oauth2redirect",
     "AndroidDataScheme": "issuehub",
      "AndroidDataPath": "/oauth2redirect"
}

Xamarin 製アプリを実機にデプロイするのは、自分でも新規にプロジェクトを作成すると毎回つまずく。 使うまでの道のりは険しい。 マネタイズできそうなアプリを他に作ることができて、 Apple Developer の登録料を払ったら、 IssueHub もついでに登録する、かもしれない。