解せぬ日記

雑な話をする

Arch LinuxのHeroku CLIパッケージのメンテナになるまでにやったこと

メンテナになったと言えば聞こえは非常にいいのだけど、つまるところArch Linux用のHeroku CLIパッケージがなかったので作って公開して使えるようになったということだったりする。まあサイトにはメンテナとして名前が載ってるし、メンテナなんだろうと思う。何らかのOS用のパッケージ配布自体は初めてで、作る上で色々と人々に教えを乞うたら、Arch Wikiには書いてなかった気がすることも教えてもらえたのでまとめておくことにした。

f:id:terut:20170611171250p:plain

成果

何はともあれ、成果になります。

aur.archlinux.org

モチベーション

パッケージを作っていた時点のGo/NodeベースのHeroku CLIのドキュメントには、Apt以外で管理する場合、スタンドアロンバージョンを使うように書いてあり、自分でファイル管理をしないといけなかった。とにかくパッケージとしてシュッとインストールしたり、アンインストールしたかった。作っていた時点と言ってるのは今はドキュメントが変わっているからで、それについてはあとの方で触れようと思う。

パッケージの作り方

Arch LinuxではPKGBUILDというファイルを使ってパッケージのビルドの手順をシェルスクリプトで書いていく感じになるのだけど、まず最初はArch Wikiをよく読むとよい。パッケージに関してもArch Packaging Standardsとして基本的な作成方針がまとめられている。他にも基本的な作り方であれば、ググれば見つかるのでそういう類の情報は書かない。勢い良くブラウザバックして別のページを探すとよいと思う。

Arch packaging standards - ArchWiki

最初作ろうとしたもの

僕が作り始めた当初はGolang/Nodeで書かれたHeroku CLI v5がGithubのmasterで開発されていた。プラグイン機構がNodeで実現されてるからだと思う。Nodeベースのv6系もリリースはされてはいたが、v6ブランチ上で開発されていたのとGolangのコードがまだあったため、masterで開発されているGolang/NodeのHeroku CLIをパッケージ化しようと試みた。ただHeroku CLIはそれ自身に自動アップデートの機構をもっているため、バージョン指定でバイナリを取得できず、ステーブルという形でのダウンロードになってしまって、どのバージョンなのか分からない状態だった。VCSなどから直接コードを持ってきてビルドするタイプのパッケージは、gitコマンドなどからバージョンを取得してパッケージのバージョンを動的に決めていたので、それを真似してダウンロードしてきたバイナリの実行結果からバージョン情報を解析してバージョンをつける方式をとった。

最初のレビュー

基本的なことは既に述べたようにPackaging Standardsを読めば分かるわけだけど、不安がある人はBBSとかでレビューしてもらいましょうと書いてあったので、作ったPKGBUILDのレビューをお願いしてみた。そうするとWikiに書いてなかったようなアドバイスを色々ともらえた。

  • ダウンロードしたものの検証にはsha512を使ったほうが良いこと
  • 検証をSKIPする指定は、VCSなどからのソースビルドをするようなパッケージに使うこと
  • pkgver()はVCSを使うパッケージ用なので使うのはやめたほうがよいこと
  • バージョンを固定できないため、VCSで使うような方法をワークアラウンドとして採用してるみたいだけどセキュリティホールになるので、バージョンが固定されたアーカイブを探したほうが良いこと
  • ライセンスファイルのコピーの正しい方法
  • installコマンドを使ったほうが良いこと
  • メッセージを出すためにはprintじゃなくてechoやcatとヒアドキュメントを使うこと
  • ~/.local/shareなどのHOMEをいじるようなメッセージは出さないこと
  • HOMEをいじるような場合はスクリプトを用意し、かつ"${XDG_DATA_HOME:-~/.local/share}“のようにXDG_DATA_HOMEを使うこと
  • ソースとバイナリはライセンスが違う場合があること

それでバージョンを固定するには自分でビルドするしかないなと思ったのだけど、Golang/Nodeで書かれているため、普通のgo buildとかではなく、Makefileと格闘するハメになった。

Herokuへの問い合わせ

Makefileでやるのはビルドできなすぎて辛すぎたので、Heroku Supportで問い合わせしてバージョン固定でバイナリをダウンロードできないのか聞いてみた。そうするとNodeバージョンを使うと良いよというアドバイスと共に、Heroku CLIのドキュメントを見てと言われたので眺めてたら、全体的にアップデートされ、ArchやBSDは自動アップデートなしのNodeバージョンを使ってくれという感じになっていた。GithubのmasterもNodeベースに切り替わっていてGolangの実装は全てなくなっていた。CLIのコア部分はcli-engineというFrameworkに依存してたので、Golangの実装はv6で使ってなかったのかも知れないし、以前から切り替える計画があったのかもしれないけど、問い合わせてから2-3日でやるのはさすがに強い。これが世界のやっていきですかという気持ちになった。結果としてNodeのライブラリとしてバージョンを指定してインストールするパッケージを書けば良くなった。

2回目のレビュー

1回目のレビューの時と違い、Nodeのライブラリをパッケージとして提供してるものは既にあったので、探してマネをした。ただAURで公開されてるライブラリは正しいやり方をしてないものも多いし、PKGBUILDのオートジェネレータがおかしなビルド方法を提供していたりるので、気をつけたほうが良さそうだった。既にコミュニティパッケージになっているものを参照してリリースにこぎつけた。正直Nodeとnpmがあれば普通にインストールできるのだけど、Node使う環境にいない人にとっては、依存を解決して全部入れてくれるし多少は便利かなと思ってリリースすることにした。レビューのスレッドを一応貼っておく。

[SOLVED] PKGBUILD review request: heroku / Creating & Modifying Packages / Arch Linux Forums

最終的なPKGBULDの様子。

github.com

今の悩み

めちゃめちゃHeroku CLIのリリーススピードが早くて、一日に2回リリースされたりとかザラだったりする。そんな感じなので最新バージョンに違いが出やすくて、マイナーバージョンの違いでもOut-of-dateフラグ、つまり古いぞフラグをパッケージにつけるマンがいる気配を感じている。僕はどうしたらいいんだろうか。

まとめ

とにかくHeroku強い。世界のやっていきを見た。

英語をやっていくためのDuo3.0デスクトップアプリを作った話

以前、英語をやっていくためのDuo3.0アプリを作り直した話 - 解せぬ日記で書いたとおりReactNativeで作ったモバイルアプリを使ってDuo3.0をやってきたわけだけど、それをやっていると今度はデスクトップアプリが欲しくなってきた。やっぱりタイピングはそっちのほうが早く時間も節約できる。僕は普段、ArchLinuxを使ってて、仕事ではMacOSXを使っているので、気がついたらElectronを使って作ってた。Duo3.0アプリ芸人みたいな様相を呈してきたけど、僕はボキャビルがしたいだけなんだ。

成果

以下、成果になります。

vimeo.com

ソースコードも置いとく。小さいのでElectronやってみたい人が雰囲気を眺めるには良いかも知れない。

github.com

TL;DR

Electronに関してはサンプル程度だが前に書いたこともあり、中身はJavascriptとHTMLで書くということで、React入れて、ロジックとJSX書いて、トランスパイルして、JS読み込むという感じなのでシュッとアプリを書くには非常によい。

実装に関して

難易度

今回はデスクトップアプリとはいえ、メニューバーに常駐したりするわけでもなく、アプリ自体のメニューとかも別にどうでも良かったので、Electronで実装されてるAPIにはあまり触れてない。MenuItemとかTrayとかそういう類のやつ。たぶんこの辺を触り始めると、これぞElectronという特有の知識が必要になって、難易度も上がるんだろうと思う。そこを触らないのであれば、APIを眺めることもあまりないと思う。

アプリサイズ

これはよく言われてることで、そのままビルドすると異常なサイズになる。普通にやってしまうとnode_modulesとかも一緒にいれてしまうのが原因で、いちいちnode_modulesやら必要ないものは外す必要がある。最初、何も考えずにビルドしたら400MBぐらいあってウケた。色々含めないようにして100MBぐらいになった。音声ファイルが40-50MBぐらいあるので、まあそんなもんかなという感じ。

音声の再生

これはもうHTML5使えるからめちゃめちゃ楽でaudioタグを使って終わり、何も難しいことはない。FlutterやReactNativeでは大変だったので有り難し。

Diff

モバイルアプリを使ってて見にくいなーと思ったのが、一瞬でどこ間違ったか分からないことだった。間違った箇所を確認するのに時間を取られてしまい、効率的ではない。ということで、diffを導入して、差分で間違ったところだけを赤くするようにした。diffライブラリを使っていて、addedのステータスの単語を赤く表示している。ただこれ回答側に余分な単語、例えば冠詞がないのに冠詞をつけていた場合は、何も表示されないのでなんとかしたいが、いいUIが思いつかない。とりあえずaddedだけでも必要十分な気がしたので、これでやっていってる。

まとめ

全体としてシュッと出来たなという感じ。フロントエンドマンならもっと早くできるんだろう。2ヶ月前ぐらいに作って、これ使ってやりまくってたら、45セクションを2日で終わらすみたいなこともできるようになってきて、めでたい。何気にセンテンスのjsonを別の例文に変えても使えるので、新しく覚えたい例文とか足していってもいいかもしれないですね。

やっていき。

英語をやっていくためのDuo3.0アプリを作り直した話

先日、Flutterで英語をやってくためのDuo3.0アプリを作ってみた話 - 解せぬ日記というのを書いたわけだけど、そのアプリに音読用の機能が欲しいなということで追加を検討した結果、Flutterで作ったものをReact Nativeで作り直した。

成果

以下、成果になります。

vimeo.com

ソースコードも置いとく。スタイルやらちょっと整理したい。

github.com

TL;DR

React NativeはJavascriptとReactという慣れた言語で書けることや割とライブラリとかもあって良い反面、iOSAndroidAPIを分けたりしてる部分があったりして、そこがすごく気になった。コードを無理に共通化しようとすると分岐が出てきて、なんとなくそのうちやっぱSwiftやJavaで作り直したほうがいいねとなるかもしれない。Dartという言語で新しいFrameworkを学ぶ必要があり、対応してるAPIはまだ少ないものの僕個人としてはFlutterが実現しようとしてることのほうが好きだし、コードも楽しく書けた気がする。シンプルなものはFlutterでiOSAndroidを動かし、それなりの表現がしたいものはSwiftやJavaで個別に作るみたいな流れが好ましい。FlutterはGoogleが作ってる新しいOSのFuchsiaのUIレイヤーに採用されるみたいで、コミットも活発だし期待したい。

作り直した背景

音読機能を追加するにあたり、音声を流すための調査をした。その結果、Flutterには現在のところ音声ファイルを扱えるAPIが存在しないということが分かった。正確にはメンテする人がいないということでオープンなリポジトリからはAPIを削除したとのこと。Privateリポジトリに移したから使いたい人やメンテしたい人はgoogleグループに連絡くれとのことだったのだけど、前に公開されてたコード読んだ感じ、結構難しかったのでReact Nativeで作り直すことにした。

React Nativeを選んだ背景には、音声ファイルを扱えるライブラリがあったこと、AndroidiOSのアプリをビルドできたこと、そしてReactとJavascriptをもう少し触っとこうと思ったことがある。個別に作りたいアプリじゃないし、iPhoneを使ってる妻も使えるようにということで、FlutterだったりReact Nativieを選んでる。

実装で詰まった部分

ReactとJavascriptで書けるので、まあなんとか書けたものの設定やらなんやらで割とハマった。thisを束縛したりとかはReactでお馴染みだったので、React書ける人ならビルド周辺以外はハマることは少ないような気はする。ライブラリも結構色々あって探せばなんかありそう。

Buildの設定

React NativeのCLIから作られるアプリの中にはAndroidiOSディレクトリがあり、それぞれBuildに使うための設定ファイル群が生成される。僕はAndroidをサンプル程度のものしか作ったことがなくて、あまり知識がないのだけど、React Nativeは結構Androidのビルドの仕組みなどの知識を要求してくる感じだった。iOSはまだBuildしてないが、Info.plistなどが生成されてるし、まあ多分同じなんだろうと予想している。

build.gradleにはAndroidSDKやBuildツールのバージョンが書かれていて、Android Studioをインストールした時に入るSDKのバージョンによってはSDKやBuildツールの対象バージョンをインストールするか、設定ファイル自体を対応するバージョンに書き直す必要がある。React NativeのCLIファイルが作るファイルは全てSDKが23でBuildツールが23.0.1だったので、もしかしたら書き直すことの方が多いかもしれない。多分基本的なことなので、トラブルシューティングにも書いてない。

けどよくよく考えれば、公開するために署名やProfileなどの話は避けて通れないし、どちらにしろBuildに関しては抑えておく必要はありそう。

音声ファイルの置き場所

これも考えれば当たり前なのだけど、AndroidをBuildするときに全部がbundleされるわけじゃなさそうで、音声ファイルが参照できなかった。そこでres/rawに入れたのだけど、ここってディレクトリ管理できないっぽくて、そのまま直下に置いた。assetsとして管理すればディレクトリ管理もできそうなので、Androidマン頼む教えてくれ。自分でドキュメント読めという話かもしれない。

APIの分岐

これはハマったというより悩んだという感じだったのだけど、iOS用のコンポーネントが用意されてたり、Android用のAPIが存在してたりして、一つのソースでやろうと思うとまあそうだよねって思うものの、非常に考えさせられた。当然、個別に用意されてるものを使ったほうが実装が楽だったりで便利そうな感じはすごくしたのだけど、そうすると再利用できる部分はどんどん減っていく。それで行き着く先は、個別に作ったほうがいいんじゃね?ってなりそうだなと思ったんだけど、そんなことないよって実際作ってる人がいれば話を聞いてみたい。

まとめ

無事に音読用の機能を追加できた。とりあえずこれでオフラインでも英語の勉強には困らなそう。人の目を盗んでは一人でブツブツ言うということもできそうだ。どっかの綾瀬はるか似がDuo 3.0やってるって言ってたし、Javascript界隈に詳しいっぽいので、色々ReactとかJavascriptの書き方教えてくれると嬉しい。多分Flutterが音声ファイルを流すためのAPIに再度対応したらFlutterの方に機能追加して、そっちをメインにすると思う。FlutterはGoogleが作ってる新しいOSのFuchsiaのUIレイヤーに採用されるみたいで、コミットも活発だし多分追加してくるだろうと予想してる。

やっていき。

Flutterで英語をやってくためのDuo3.0アプリを作ってみた話

前回、FlutterとDartをしばらくやってみると言ってたこともあって、土日にアリアンデルに籠りながら、Flutterをやっていた。アリアンデルというのはダークソウル3のDLCのことですので、アリアンデルの絵画世界に興味がある人は一緒にやりましょう。

TourとかDart bootstrapとかを眺めたり、実際に動かしながら見ていたわけだけど、途中から眠くなって、アプリでも作りながら困ったらドキュメントに戻ればいいかーといつもの雑さでアプリを作り始めた。 最近、Duo3.0をやってるので、オフラインでもディクテーションとか暗記(覚えれば音読をいつでもできる)とかできるといいよねって思って、Duo3.0アプリにした。

成果

以下、成果になります。

ソースコードも置いとく。

github.com

TL;DR

FlutterのUIはシンプルなものなら割と作りやすいと思う。HTMLっぽさがあってとっつきやすかった。ただその分、オブジェクトを生成しまくってて大丈夫なんかなとも思った。完全にFlutterという言語を書いてる気分で、AndroidiOSのこと知らなくてもそれなりのものは書けそう。

StatelessとStatefulなWidgetがあって、Reactiveな仕組みが導入されてるのはやっぱり良い。Stateを変えるとWidgetを生成し直しているようで、差分更新って書いてあった気がする。Reactにインスパイア受けてるってことで、ReactNativeに似ているような気がした。

困ってもExamplesが結構充実してるのでなんとかなることもあるんだけど、ドキュメント、Examples、テストコードで見当たらないとググったところでほとんど情報が出てこないのが最高。ただ最終的には実装の詳細が見れるので、まあ良いかなとも思う。

実装で詰まった部分

基本的にはStatelessかStatefulなWidgetどちらかを選んで実装していく。setStateのコールバック内でデータを設定すると、再レンダリングが走る感じ。ReactNativeとかもそんな感じだったような? よく分からないことだらけで、細かくあげるとキリがないので、幾つか載せておく。

画面遷移

まず画面遷移からしてよく分からんかった。最初にRoutingの設定をするんだけど、実際の呼び出しでは引数を渡せなさそうな感じで、これオブジェクト渡したい場合どうするんだろうってなった。多分現時点では渡せなくて、GithubのIssueに次のマイルストーンのタグがついてたからそういうことなんだろう。 人によってはRoutingを"/section/$section_id"とかして、Hookを使って自分でパースしたりしてたけど、マジで言ってる?ってなって他の方法を探した結果、名前で遷移するだけじゃなく、Routeオブジェクトを生成して遷移できそうだったので、そっちでWidgetオブジェクトを初期化して遷移することにした。 マジで言ってる?とか思った割に僕もWayfaringとかで同じことしてるんで、何も言えないことに後で気づいた。

実際のコードは以下のような感じ。

// 1. 設定するパターン。これだと引数が渡せない。
void main() {
  final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
    '/': (BuildContext context) => new SectionList(),
    '/sentences': (BuildContext context) => new SentenceList(),
  };

  runApp(new MaterialApp(
      title: 'Duo 3.0',
      initialRoute: "/",
      routes: routes
      ));
}

// 適当なハンドラで遷移 。pushNamedはFutureオブジェクトを返すんだけど、評価後のオブジェクトにWidgetオブジェクトが返ってくるわけじゃないので、詰み。
void _handleSectionSelected(Section section) {
  Navigator.pushNamed(context, "/sentences");
}
// 2. Routeオブジェクトを使うパターン。引数が渡せる。
void main() {
  runApp(new MaterialApp(
      title: 'Duo 3.0',
      home: new SectionList(),
      ));
}

// 適当なハンドラで遷移
void _handleSectionSelected(Section section) {
  final route = new MaterialPageRoute<Null>(
      builder: (BuildContext context) => new SentenceList(section: section),
  );
  Navigator.push(context, route);
}

InputのonSubmittedが発火不可

InputオブジェクトにはonChangedとonSubmittedってコールバックがあって、打ち込まれたセンテンスの判定に使うかーと思ってたら、キーボードのReturnをDoneに変える方法が分からなくて詰んだ。onSubmittedはDoneを押した時に発火するっぽい。エミュレートできそうなperformActionってメソッドがあったけど、なんかうまくいかなかった。 あとInputはPlaceholderを入れられるんだけど、Inputだけで使うとPlaceholderの文字が残り続ける。なんで、普通にFormオブジェクトとInputFormFieldを使うのが良さそうだった。InputFormFieldはvalidatorを設定できるのだけど、初期表示時と入力するたびにチェックが走ってウケる。この辺はExamplesかテストコードを眺めると使い方が分かる。

日本語が表示不可

日本語表示しようとしたら表示できなかった。これはフォントを変えりゃイケそうってことで、TextStyleで色とかフォントファミリーとか選べるので、それで設定すれば良い。何となくHTMLぽさが少しある気もする。

final text = new Center(new Text(
                        "sample",
                        style: new TextStyle(fontFamily: "Hiragino Kaku Gothic ProN"),
                        ),
                   );

まとめ

ドキュメントもExamplesも充実してるし、テストコードを見ればハマりながらもなんとか実装できる。どれにもない場合は、コード読んで自分で考えるしかない。ググってもマジでほとんど何も出ない。 Rectiveな仕組みをデフォルトでサポートしてるのは非常に良い。Androidは確か既にサポートしてた気がするので、iOSも是非お願いしたい。Reactっぽさがすごいあるので、その辺に慣れてる人は入りやすいと思う。 完全にFlutterという言語を書いてる気分で、AndroidiOSのこと知らなくてもそれなりのものは書けそう。 使ってるうちに音出したいなーと思ったんだけど、"Remove sound.dart"というコミットを発見してウッてなってるのが今。

Flutterに興味が出た人はこちらも参考にするといいかもしれない。

terut.hatenablog.com

話は変わるが、この間"やっていき宣言"なるものが行われ、いいなーと思ったので、おっさんだけどやっていくことにした。

yatteiki.fm

異常に怠惰なので気を抜くと気持ちだけ、一発にもならない何かみたいなので終了してしまうのが正直なところではあるが、気負わずやっていき。

Flutterの開発環境を作った話

image

なんとなく思い立ってFlutterでも触ってみようかなと思って、開発環境を作ってみた。 FlutterというのはGoogleが作ってるiOSAndroidを一つのソースで開発できるというフレームワークで、Dartを使っているらしい。ハイパフォーマンス、iOSAndroidのUI/UXの再現度が高いことが売りとのこと。 high-fidelityと書いてあったので、そういうニュアンスで捉えてるけど、間違ってたら教えて欲しい。

Welcome to Flutter! - Flutter

とりあえず上記のサイトに従ってやっていけば良さそうだったのだけど、ちょっと詰まったところとか試したことが幾つかあったので、そこだけメモとして残しておく。

TL;DR

素直にAndroid StudioIntelliJ入れるのが楽で、Atomプラグイン新しくならないのでやめたほうが良い。あとはJava 1.8を使えば、特に問題なくiOSAndroid共に起動できる。

IDEのインストール

Setup作業を進めていくと、Android StudioIntelliJの2つのIDEをインストールする流れになる。 Android StudioAndroid SDKAndroid SDK Platform-Toolsを入れるためにインストールし、コードを書くのに最高の体験を得たければ、Pluginを用意したんでIntelliJを使いましょうということっぽい。 コマンドラインツール使うなら、エディタは何でもいける。

IDE入れまくるのやだなー、Android StudioってIntelliJがベースじゃなかったっけ?と思って、Android Studioプラグインを検索してみると、DartはあるけどFlutterは見つからなかった。 ググるAtomで開発してたりして、確かにDartとFlutterのプラグインはあるけど、そもそもflutter doctorの結果が違うことに気づいて調べてみると、Atomプラグインはメジャーバージョンアップしないってことだったので、プラグイン使うなら大人しくIntelliJが良さそう。おそらくちょっと前まではAtomが推奨されていて、コマンドラインの結果がAtomで表示されてたってことなんだと思う。

[-] IntelliJ IDEA Community Edition (version 2016.2.5)
    • Dart plugin not installed; this adds Dart specific functionality.
    • Flutter plugin not installed; this adds Flutter specific functionality.
    • For information about managing plugins, see
      https://www.jetbrains.com/help/idea/2016.2/managing-plugins.html

https://groups.google.com/forum/#!topic/flutter-dev/JEzfu0kpmKA

まあよくよく考えるとAndroid StudioiOSアプリを起動するのもウケる気がするので、複数のIDE入れなってことなのかな。

Javaのバージョンについて

Setupして実際にデバイスを指定して起動しようとすると、エラーが出た。

github.com

Java 1.8が必要そうだったので、ダウンロードして入れたらiOSAndroid両方で動いた。

まとめ

一つのソースで開発できるの最高!みたいな話なのかもしれないけど、現実問題としてiOSAndroidでの最高の体験を求めた場合、設計思想の違いによりUIは違うものになると思うので、一旦落ち着いて考えていきましょうという気持ちはありつつも、ビューだけ分けるとかなんかそういうのもあるかもしれないし、見てみるのは良いと思う。 Dart触るの初めてなので、しばらく楽しんでみることにする。

Arch LinuxでActivitiesを使うとカーソルが消える話

TL;DR

Gnome3を使ってて、Activitiesをクリックしたら波アニメーションの後にマウスカーソルが消えてしまうという人へのワークアラウンドは以下。

$ yaourt -U /var/cache/pacman/pkg/mutter-3.18.2-1-x86_64.pkg.tar.xz

Arch Linux kawaii.

背景

いつの頃からか忘れたけど、Activities Hot Cornerを使うときにマウスカーソルが消えてしまって、異常に不便になった。 Activities Hot Corner(アクティビティ)という呼び方が正しいのかもわからないけど、ArchWikiにはそう書いてあったからそう呼ぶことにする。

以下のサイトの最上部にある画像のようなメニューとワークスペースとウィンドウが俯瞰できる状態のときに消えてしまうので、Tabとかで選択してた。

www.gnome.org

解決法

色々調べた感じだとmutterが悪いんじゃないかという当たりをつけたので、キャッシュからダウングレードを実行する。 ちなみにmutterは何かというとGNOME用のウィンドウマネージャとのこと。

$ yaourt -U /var/cache/pacman/pkg/mutter-3.18.2-1-x86_64.pkg.tar.xz

ただこれはワークアラウンドでしかないので、ちゃんとアップデートを確認しつつ、アップデートが出たら上げていくのがよいと思う。

Bugsにちゃんと報告上がってた。

bugs.archlinux.org

Alamofireで204 No ContentなAPIのレスポンスをどう扱うか

https://media.giphy.com/media/aWjpyBmKXQRAA/giphy.gif

TL;DR

現状、SwiftのHTTPクライアントであるAlamofireで204 No ContentなAPIの処理のベストプラクティスは存在しないので、responseで受けるか、Response Serializerを作るか、自分でResponseを作って泥臭くやるしかない。 Response Serializerを作るのが、他のAPIの処理と同じように実装できていいように僕は思う。

背景

Swift 2.0とiOS 9対応をしていて、Alamofireのバージョンが上がったことで、AlamofireがResponse(Struct)とResult(Enum)を持つようになった。

それに伴い、responseJSONresponseStringなどはデータが返ってくることを期待した実装に変更され、通信完了時のハンドラにResultを含むResponseが渡されるようになった。 (以前、使っていたバージョンではそういったものはなく、Request, Response, Data, ErrorがOptionalで渡される実装だったので、自前でResponseを定義して扱っていた。)

// 以前: ハンドラにrequest, response, json, errorのOptionalが渡されている
Alamofire.request(urlRequest)
    .validate(statusCode: 200..<300)
    .responseJSON { (request, response, json, error) in
        // code     
     }

// 現在: ハンドラにResponse<AnyObject, NSError>が渡される
Alamofire.request(urlRequest)
    .validate(statusCode: 200..<300)
    .responseJSON { response in
        // code     
     }

response以外の実装ではデータが返ってくることを期待しているので、204 No Contentの場合にシリアライズエラーとなって処理が失敗したことになってしまう。

対策

対策は幾つか思いつく。

  • responseresponseJSONresponseStringから呼び出されるメソッドで、通信完了時に呼び出されるハンドラにRequest, Response, Data, ErrorがOptionalで返ってくるので、他のAPIとは別の方法でデータを処理する
  • responseとAlamofireのResponse、Resultを組み合わせて、他のAPIと同じ処理ができるようにする

けど上記の方法はいずれもアドホックな対応になり、微妙な気がしてやめた。

Issueを巡っていると、コミッタの人が例えばNo Dataを扱うResponse Serializerなんかあったらどうかみたいな話をしていて、Response Serializer作れることが分かった。 Response Serializerを作るとresponseJSONと同じようにレスポンスを扱えるので便利そうだし、作るぞ!!という気持ちになったので作った。

github.com

実装

ドキュメントにXMLのResponse Serializerのサンプルがあったので、これを参考にした。

https://github.com/Alamofire/Alamofire#creating-a-custom-response-serializer

extension Request {
    public static func NoContentResponseSerializer() -> ResponseSerializer<AnyObject, NSError> {
        return ResponseSerializer { request, response, data, error in
            guard error == nil else { return .Failure(error!) }

            guard let _ = data else {
                let failureReason = "Data should not exist. Input data should nil."
                let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
                return .Failure(error)
            }

            return .Success("")
        }
    }

    public func responseNoContent(completionHandler: Response<AnyObject, NSError> -> Void) -> Self {
        return response(responseSerializer: Request.NoContentResponseSerializer(), completionHandler: completionHandler)
    }
}

.Success("")で空文字を返してるのはちょっとした理由があって、responseJSONResponse<AnyObject, NSError>を返してくるので、そこを揃えたかったため。

本来であればnilを入れて返したいところで、もうちょっとうまい実装があればOptionalとして返したりできそうだが、OptionalとAnyObjectを同じように扱うにはAnyを使う必要があって、現時点ではSwfit 2.0とiOS 9対応を優先させて、とりあえずこれで動くようにしてる。

あとは呼び出し側で、

Alamofire.request(urlRequest)
    .validate(statusCode: [204])
    .responseNoContent { response in
        // code
    }

のように呼び出せば良い。

まとめ

他にいい方法があったり、こんなやり方イケてないだろって話があれば、ぜひ教えていただきたく、何卒。 AnyObjectは割とよく使うけど、Anyって結構使うもんなんだろうか…。