タグ: iOS

Xamarin.iOSでのストーリーボードを使わない開発

 Visual Studio で Xamarin.iOS のプロジェクトを作成すると、ストーリーボードなどという忌まわしいものがデフォルトで作成されてしまい非常に厳しい気持ちになります。この記事では、それらを削除して、コードでUIを構成していくためのステップを説明します。3分ほど作業をすれば邪魔なものが消えてなくなってくれます。

1. Main.storyboard を削除

2. ViewController.designer.cs を削除

3. ViewController の余計なコードを削除する

ViewController が partial class である必要がなくなったので、partial キーワードを消します。合わせて IntPtr を引数にとるコンストラクタも不要になりました。

4. Info.plist の Main Interface を削除

5. AppDelegate に ViewController を表示するコードを追加

6. 動くサンプル

GitHub で公開しているので適当にどうぞ: https://github.com/pawotter/Xamarin.iOS.NonStoryboard

DelegatingHandlerを用いた快適なiOSアプリのHTTP通信周辺環境の整備

 昨今のネイティブクライアントアプリで、 HTTP 通信を行わないものはないと言っても過言ではないくらい、アプリ開発の基本中の基本だと思います。きっと、開発を行う中で HTTP 通信の前後に色々な処理を挟んでみたいという気持ちが湧いてくるのではないでしょうか。たとえば以下のような話です。

  1. リクエストの内容をログ出力したい
  2. 電波状況が悪いときはリクエストを走らせないようにしたい
  3. 通信中はネットワークアクティビティインジケータを走らせたい

 そんなときに System.Net.Http.DelegatingHandler はとても便利な代物です。公式のドキュメントはこちら: https://msdn.microsoft.com/ja-jp/library/system.net.http.delegatinghandler(v=vs.110).aspx

1. HTTP リクエストのログ出力

 たとえば、Http リクエストの内容をログ出力したいとすれば、以下のような簡単な DelegatingHandler のサブクラスを作ってあげるとよいでしょう。

 実用的には Console.WriteLine をクリティカルセクションに配置したほうが良いかもしれませんが、ひとまずこのような単純なコードでリクエストの手前に処理を挟むことができました。使い方は、単純にこれを System.Net.Http.HttpClient のコンストラクタに渡すだけです。これで全てのリクエストの内容がログ出力されます。

2. 圏外時にリクエストを送らない

Reachability を使えば簡単です。

3. 通信中にネットワークアクティビティインジケータを回す

 ネットワークアクティビティインジケータの管理、非常にめんどうですよね。通信が同時に複数走るのはザラなので、そのあたりを考慮する必要があると思います。だいたい皆さん同じような実装をすると思うのですが、今走っている通信の数が正であれば、インジケータを回しっぱなしにして、0になったら止めるみたいなカウンターを作るのではないかなと思います。ひとまずステップをわけて実装を見ていきます。

3.1. NetworkActivityIndicator の抽象化

 ネットワークアクティビティマネージャは基本的に ON/OFF のインターフェースさえ持っていれば十分でしょう。それぞれActivate(), Inactivate() という名前のメソッドとします。実装クラスは、UIApplication インスタンスを外から受け取り、Activate(), Inactivate() のタイミングで NetworkActivityIndicatorVisible の値に true, false をぶち込んであげれば良いという話になります。

3.2. 走っているリクエストをカウントするクラスの作成

 同時に走っているリクエストが1つ以上ある場合に IIndicatorActivate() を呼び出し、通信が走っていない状態になるとき Inactivate() を呼び出す簡単なカウンタークラスを作りましょう。

 このカウンタークラスは、通信開始時には Attach()、 終了時には Detach() を呼ぶ簡単なインターフェースを持つことになるでしょう。それぞれインターフェースと実装コードは以下のようになると思います。

3.3. DelegatingHandler の実装

 準備ができたので、今まで作ったものを利用してネットワークアクティビティインジケータを通信が走っているときにクルクルまわす DelegatingHandler を実装しましょう。以下のようになります。

 コード全体を GitHub で公開しているので使いたい方はどうぞ。MITライセンスです。 Xamarin.iOSに依存するパッケージの nuget への放流方法イマイチよくわからんので有識者の方、教えていただけると嬉しいです

  • Repository: https://github.com/pawotter/NetworkActivityManager

3.4. Swift での実現方法

 URLSessionラップして、自分で HttpClient 作ればできるよ。実装はだいたい同じ。実装的にもパフォーマンス的にも改善の余地があるコードですが、わかりやすく伝えるとするならば以下のような実装コードになる気がします。

5. おわりに

DelegatingHandler ベンリすぎワロタ。あとこれを使うとリトライ処理とかも挟めてとってもベンリ&ベンリです。良いですね〜。

iOSDC Japan 2017にてベストトーク賞をいただきました

 2017/09/15〜17 の 3日間にわたって開催されたカンファレンス iOSDC Japan 2017 に参加してきました。私は2日目の 14:20〜14:50 に Track A 会場にて「RxSwift の Observable とは何か」というタイトルでセッションを持たせていただきました。

 このまわりの話をちゃんと解説しようとすると、最低でも60分は必要かなと思っていたのですが、最長の枠が30分しかなかったのでやむなしで CfP を提出しました。発表内容については以前からコードを追っかけてた分野だったので改めてする作業もそんなになく(とか思ってたんですが、以前に真面目にコードリーディングしたSwift1.2/2.x時代と比較してかったるい diff とかがあって地味に胃が痛かった)、スライドとか口頭発表原稿とかを書き起こす程度でした。ただ当時の自分は頭がおかしかったようでスライドを Illustrator + In Design で作り始めてしまい、次回から素直にパワポで作ろうと思いました(細かいレイアウトを指定できるのはいいけどめんどくさかった)。

 お話の構成をするなかで、300lines近くの実装コード解説をどうやったら、スライドとトークの内容で聴いていただく皆さまに理解してもらえるかをだいぶ悩みました。ただでさえ、発表のスライドにコードがあるとウッとなるはずなのに、それを30分間ひたすらやり続けたら、地獄でしかないので、この部分をクリアする点に力を注ぎました。そこで、ベースとなるソースコードを頭のスタック領域に置いていただくための説明に発表の半分以上の時間かけて、その上で、Reactive Extensions 特有の diff を解説するというトーク構成にしようと決めました(発表の7日くらい前に)。

 実際発表のリハーサル的なものを自分一人でやってみたんですが、どうやっても45分くらいかかってしまい、泣く泣くスライドのタイトルに(余談)という文字列を入れて、口頭発表の内容からは削ったりしました。来年から45分枠とかほしい…。

 今回のお話は決して技術的レベルの高い話ではなくて、まとめてしまうと有名な設計パターンとその発展形についてのお話でした。他の方のセッションをいくつか拝聴させていただいたのですが、私よりも技術的にはるかにハイレベルなことをやっていたり、普段自分が無意識にやっていてわりと共感できるようなお話がいくつもあって楽しかったです。

 特に面白かったセッションは @k_katsumi さんの Building High Performance and Testable UI component で、自分が普段 iOS のプレゼンテーション層を書くときの考え方とだいぶ近くて非常に共感できました。自分のやっている方法を自分自身でしっかり論理的根拠を持ってある程度正しそうという気持ちを持てるようにできるのが一番いいですが、やっぱり似たような手法を他の方もやられているという話を聞くと気持ち的には安心できますね…。

 あとは @kitasuke さんの Introducing protobuf in Swiftのあとに、Ask the speaker まわりで protobuf vs swagger みたいな話ができて楽しかった。サーバー〜モバイルクライアント間での protobuf 利用がどのようにされているのかの事例、ちゃんときいたことがなかったので興味深かった。あと protobuf とエラーハンドリングまわりの話も大変参考になった。最後には、結局APIクライアントを人間が書くことを撲滅していきましょうみたいな気持ちをみなさんと共有できたところがよかったですね…。撲滅していきましょう。

 素晴らしいセッションがたくさんある中、私の稚拙な発表を聴いていただいた皆さま、運営スタッフのみなさまなど本当にありがとうございました。そしてまさか自分がベストトーク賞をいただけるとは本当に思っていなかったので、LT枠の無限ビールタイムの時間にビール4本飲んでいい感じに意識があいまいになっていました。粗相してなかったですか?大丈夫ですか?

 ベストトーク賞の商品としてサイバーエージェントさんから素晴らしいお品をいただけるようで本当にありがとうございます。アベマTVさん最高のサービスです。アメーバブログさんでよく声優さんのブログ記事と写真を見ています。最高のサービスです。いただいたディスプレイでアベマTVさんとアメーバブログさんを見ます!

アメーバブログさんで特に見ているのは以下のブログです

アメーバブログさん最高のサービスです。私も声優さんのブログになりたい。

 2日目の発表が終わったあと、残念ながら Wake Up, Girls! 4th Live Tour 東京公演 夜の部がイベ被りしており、懇親会には参加できませんでしたが、来年 Wake Up, Girls! のイベントが被っていなければ懇親会に参加したいなと思います。ハッシュタグ見てたら美味しそうな料理と充実したクラフトビールとハイレベルなエンジニアがたくさんいてとても楽しそうだった。私、ビール(詳しくはないのですが)大好きなので、スポンサーの転職ドラフトさん来年も無限ビールお願いします。

 1日目夜のスピーカーズディナーは、やっぱり真剣に技術を追っている人たちが集まっているだけあって、楽しかった。まだまだ私は技術的にも精神的にも未熟な社畜4年生で、年上の方はもちろんのこと、最近だと高校生・大学生なのに、いまから自分がどれだけ努力しても追いつけそうにない人がインターネット上にはたくさんいるので、ちゃんと技術やお仕事に真摯に向き合う姿勢だけは忘れずに生きていきたいな、というなんか真面目な文章を、最後に書き残しておきます。

ひとまずみなさまお疲れ様でした。ありがとうございました。

appendix

iOSアプリ開発の全体像

超技術書展で頒布したiOSアプリ開発の全体像をだらだら書いた本を記事として公開。
ただのポエムです。

1. iOSアプリ開発を取り巻く環境

iOSアプリ開発には、基本的にmacOSを搭載したコンピューターとXcodeとよばれるソフトウェアが必要です。もともと主にObjective-Cという言語が使われるケースがほとんどでしたが、2014年6月にAppleがプログラミング言語Swiftを発表して以後の新規開発には、ほとんどの場合Swiftが採用されているようです。またSwiftは、Objective-Cのコードと共存できるため、もともとObjective-Cで開発されていたアプリを徐々にSwiftに移行しているという話もよく聞きます。

ただし、広告やSNSなどのSDKや、幅広く使われることが予想されていて安定性が必要なライブラリについては、依然としてAPIが安定しているObjective-Cで開発が行われているケースが多いと感じます。裏を返すとSwiftは言語としてまだ成熟していないということです。Swift2系までではマイナーバージョンアップデートでも、激しい破壊的変更が行われていました。Swift3系ではある程度落ち着きは見えますが、安定しているとは言い難い状況ではあります。それでもObjective-Cに比べて平易な構文、オプショナルやクロージャといったモダンな言語機能、そして静的型付けによる安全なプログラミングなど、得られる恩恵が大きいのは確かです。

1.1. iOS, Xcode, Swift, macOSのアップデート

Appleは定期的にiOSのメジャーバージョンアップデートをリリースしています。またSwiftのアップデートもさかんに行われています。これらに対応するためにはXcodeの更新が必要になるケースが多いです。Xcodeはファイルサイズが大きく、回線速度にもよりますがダウンロードに数十分、圧縮ファイルの解凍にまた数十分かかります。さらに、新しいXcodeを入れるには、新しいmacOSを入れなければならないことが多々あります。macOSの更新に1〜2時間かかることが珍しくありません。

実際のところ、こうした大きな更新があると最悪、半日ほど開発がストップしてしまいます。また、これらの作業は開発者やCIサーバーも含め全体のマシンでやる必要があり、考慮に入っていないと意外に手痛い時間のロストとなります。

これまでの動向では、Swift下位バージョンの切り捨ては早く、新しいバージョンに素早く追従しないと最新のXcodeではビルドができないなどの問題が発生する可能性があります。Swiftを利用すると決めた時点で、遅くとも2〜3ヶ月以内に最新のSwiftバージョンでビルドできる状態に持っていくような開発体制をとれるよう、あらかじめ開発工数のバッファをとることを強くおすすめします。

1.2. Xamarinという選択肢

iOSアプリ開発にはSwiftやObjective-C以外の言語を使うこともできます。その中でも代表的なものが、クロスプラットフォーム開発ツールのXamarinです。これによりC#やF#といった言語でのiOSアプリ開発が可能になります。これらは言語としては安定していて、C#はエンタープライズ方面での実績も十分にあります。またIDEとしてVisual StudioやXamarin Studioなどを利用することができます。ストーリーボードなどのUIコンポーネントを見た目通りに配置しながら画面を作るためのツールInterface Builder相当のことも、これらのIDEで実現可能です。

Xamarin社はMicrosoft社に買収され、現在個人での利用や特定の条件下において無料で開発環境を手にすることができるようになり、徐々に普及している段階の技術です。とはいえ、すでにフェンリル社が開発を行なったNHK紅白歌合戦のアプリなどで十分な実績があり、実用に耐えうるものといってよいでしょう。

Xamarin.iOSとXamarin.Forms

XamarinのなかでもXamarin.iOSとXamarin.Formsという2つのAPI群の選択肢があります。Xamarin.iOSはiOS SDKのAPIとほとんど1対1に対応しており、SwiftやObjective-Cと似たような感覚でアプリを構成することができます。一方、Xamarin.Formsは各プラットフォームのAPIを抽象化したものを使ってアプリを構成していくため、Xamarin.Formsに特化した知識をつける必要があります。その代わり、1つのコードから複数のプラットフォームをターゲットとしたアプリを作成することができ、開発効率の向上やマルチプラットフォーム間での実装のズレを防げるといったメリットがあります。

1.3. React Nativeという選択肢

Xamarin以外にもクロスプラットフォーム開発ツールとしてFacebookが推し進めるReact Nativeという技術があります。すでにInstagram, airbnb, Facebook Messengerなど大きなアプリですでに利用されています。各プラットフォームにおけるUIコンポーネントを抽象化したReactコンポーネントを組み合わせてアプリを構成するという思想を持っています。おおよそReact.jsと同じような形でアプリケーションを構成することができます。最近流行りのReact+Redux構成を取ることもできるため、JavaScriptに詳しい方はチャレンジしてみるのもありかもしれません。

1.4 iOSアプリ開発未経験者がとるべき選択肢

iOSアプリ開発においては、基本的にiOS SDKの基本的な振る舞いを理解することは必須であるため、経験者がひとりもいないチームでの開発はObjective-CかSwiftを採用するのが良いでしょう。当然ながら書籍や情報の量、サポートできる人材の量も一番多いです。iOSアプリ開発経験者がいるのであれば、どの方法を選んでも良いと思いますが、Xamarin.Formsを選んだ場合には、iOS SDKを直接触った場合とかなり勝手が異なるはずなので、十分な技術調査・検討を行ってから採用することを強くお勧めします。

2. iOSアプリ開発の流れ

iOSアプリはおおまかに「企画」「要件定義」「設計」「実装」という流れで開発を進めていくことが多いでしょう。最終的にはAppleによる審査を経てApp Storeでの公開が可能になります。この審査は他のプラットフォームに比べて厳しく、単純で機能が少ないアプリはAppleによりリジェクトされる可能性が高いです。例えばアプリ開発の練習として簡単な電卓などを作っても、App Store上には類似のアプリが無数にあるため、公開できない可能性があるということになります。

2.1. 企画

アプリを通してどのようなユーザーにどのような体験を与えたいのか、ターゲットユーザーと実現したいことを明確にする必要があります。似たようなアプリがある場合は、それらの調査を行い、どのように差別化を図りたいのかを検討すると良いでしょう。また「App Store審査ガイドライン」に違反する内容のアプリはたとえ出来上がっていてもApp Storeに出すことができない可能性が高いため、企画を始める段階からある程度を考慮することを強くお勧めします。特にアプリ内課金を導入する際にはAppleの審査はよりいっそう厳しくなるため、審査ガイドラインに沿った形に企画を着地させる必要があります。

2.2. 要件定義/デザイン

おおまかな企画が定まったら、アプリをどのように構成するのかを考えるフェーズに入ります。ユーザーにアプリの中をどのように回遊してもらい、どのような体験を提供するのかを具体化させていきます。また提供するコンテンツやアクションの階層構造/論理構造をはっきりとさせておくと、要件やデザインに落とし込むときに混乱を防ぐことができるので、この段階で整理しておくといいでしょう。例えば電子書籍リーダーを作る場合は、本一覧がベースの階層となり、1つ深い階層にシリーズが存在します。さらにシリーズには複数の本が紐づくといった具合です。

おおまかにやりたいことが見えてきたら、プロトタイプを作成してみましょう。紙ベースで画面を描いたり、矢印で動きを表現したりといった簡単なもので構いません。またAdobe Experience Design(XD)などを使うとより高度なプロトタイピングが行えます。実装コードを書かずにおおまかな動きを見ることができるため、複数のチームメンバ間でイメージを共有するにはうってつけのツールだといえます。iOSアプリの細かなデザインについての話は3章に記します。

2.3. 設計/実装

要件がある程度定まったら、アプリの技術的な設計フェーズに入ります。また特に要件のなかで実現が難しい点があれば、本当に実現可能なのか検証してみる必要があります。具体的な話は4章以降に記していきます。

2.4. ベータテストと品質保証

開発中はCrashlytics BetaやDeployGate、小規模であれば自前でのAd-Hoc配信などを通して開発チーム内でドッグフーディングを行うことにより、アプリの不具合検出や改善などを行います。また、ある程度の状態まで達したら開発に携わっていない第三者にも利用してもらい、フィードバックをもらえるようにしておくと、開発チームでは気づけない問題が発見できるでしょう。

いよいよリリースしても問題ないという段階が近づいてきたら、アプリの動作確認項目一覧表などを作り第三者にチェックをお願いすると、より安定したアプリをストアに出すことができると思います。これは一般的に品質保証(QA)のステップとよばれることが多いです。

2.5. 審査提出とリジェクト対応

大きなアプリとなると一発で審査をパスするのは難しく、Appleから何らかの指摘を受けてリジェクトされると思っておいたほうがよいでしょう。あらかじめリジェクトされることを想定して、ある程度アプリが出来上がってきたら、とりあえず審査に出してみるのも戦略のひとつです。これによりリジェクト対応のための仕様変更が生じる点を早めに知ることができます。

2.6. リリース

審査に通過したら晴れてアプリをApp Storeにリリースすることができます。審査提出時にはリリースタイミングのオプションがあり、そこで指定されたとおりにリリースが行われます。オプションは以下の通りです。

  • 手動でリリース: 審査通過後、リリースボタンを押したタイミングでリリースされる
  • 自動でリリース: 審査通過後、自動でリリースされる
  • 指定時刻以降に自動でリリース: 審査通過後、指定の時刻を過ぎたタイミングでリリースされる

なお、オプションはiTunes Connectというアプリの管理に使われているサービスの仕様変更によって変わる可能性があります。

3. iOSアプリのデザイン

iOSアプリのUI/UXに関してはAppleが公式に「iOSヒューマンインターフェースガイドライン」を制定しています。ガイドラインに沿わないデザインや実装を行うと一部は審査でリジェクトされる可能性があるため、開発を始める前にざっくりと目を通しておくとよいでしょう。ここではアプリ全体のデザインに影響してくるポイントを数点、記述します。

3.1. なるべく標準UIに沿ったデザインにする

特段デザインにこだわりがなければ、iOSのメールやSafari、設定画面などの標準的なアプリに沿った形でUIコンポーネントを利用し、画面を構成していくのがよいでしょう。UIコンポーネントを過剰にカスタムして使うと、場合によってはユーザーがどのように利用すればよいのか迷ってしまう可能性があります。また、iOSバージョン間で見た目や振る舞いを統一するのが難しくなり、最悪の場合は特定バージョンにおいて期待した動作を提供できなかったり、クラッシュを引き起こしたりする原因にもなります。

3.2 アプリ内の画面遷移

アプリを作る際に1画面だけで構成することは非常に稀で、ほとんどの場合には画面を遷移させる必要が出てくるでしょう。iOSの画面遷移には、主に次の2つのパターンが存在します。

  • プッシュ遷移
  • モーダル遷移

プッシュ遷移とはiOSの設定画面などで見られる右方向に階層構造を掘るように遷移するタイプのものを指します。この遷移は、スワイプで戻ることができ、コンテンツ階層間の移動をスムーズに行うのに適しています。この階層構造は、提供するコンテンツの階層構造と揃えてあげるのが一般的で、ユーザーにも理解しやすい形になるでしょう。たとえば音楽プレイヤーであるiTunesアプリは、ライブラリ→プレイリスト一覧→プレイリスト→楽曲というコンテンツ階層構造になっています。当たり前のことではありますが、きちんと分類して構成されているアプリは意外に多くありません。アプリデザインの構成を考える際に同時に提供するコンテンツ階層構造を整理することは、きっとデザインをする上でも役に立つことでしょう。

一方、モーダル遷移は、ビューが現れている間はその要素内でしか操作ができないようなものを指します。例えば、iOSのSafariのブックマーク一覧はこのタイプの遷移をします。また注意喚起のダイアログなどもモーダル遷移にあたります。何かオプションを指定したり選択したりする際に使われる傾向にあります。しかし、モーダルを閉じるには基本的にはタップをする必要があるため、ユーザーに煩わしさを感じさせてしまう可能性が高まります。最近は、モーダルビューを前の画面の上に浮いた状態で表示し、モーダル画面自体をスワイプすることで閉じる実装も目立つようになってきました。これにより閉じるためにタップをしなければならない問題が解消され、モーダル遷移に対するストレスを緩和させることができるため、可能であればそういった実装も考えてみると良いかもしれません。

3.3. デバイスの大型化とデザイン

iPhone 6 PLUSの登場時には、そのデバイスの大きさに随分と驚いた方も多いのではないでしょうか。実際iPhone 5のデバイスサイズは4インチであったのに対して、iPhone 7 PLUSでは5.5インチになっています。解像度も高くなり表示領域が増え、デザイン表現の幅が広がったのは間違いありません。

しかし、デバイスが大型化する一方で、人間の手の大きさは変わっていない点には注意する必要があります。右手でデバイスを操作する場合、左上や左下に配置したボタンなどには指が届きにくく、アプリのスムーズな操作の妨げになります。以前はモーダル画面の閉じるボタンを左上に配置するケースが目立っていたのですが、近年は右上や右下などに配置されているのをよく見ます。また、先述したとおり、モーダル画面自体を上下にスワイプすると閉じることができるように構成されているものもよく見ます。本質的には触り心地が良い形に画面を構成していければ、きっと良いアプリになるのではないかと考えています。

4. アプリをどのように構成すれば良いのか

初めてアプリ開発をする際に何に気をつければよいのか、とても不安になる気持ちは非常にわかります。筆者も初めてアプリを構成する際に何かアプリ開発特有の構成・設計を取る必要があるのではないかと思い調査をしました。結局のところネイティブアプリは、裏で処理をしつつも、ユーザーの入力を常に待ち受けつづける必要があるため、UIスレッドをブロックしないように注意するという点に注意していれば、一般的なプログラミングとほとんどかわらないと思います。したがって基本的なプログラミングができる方は何も心配せず、以前から実践していた技法を使いアプリを構成することができるでしょう。

iOSアプリ開発が本当に初めてである場合は、まずラベル(UILabel)、ボタン(UIButton)、テキストフィールド(UITextField)、画像(UIImage)などの基本的なコンポーネントの使い方を覚えると良いでしょう。続いて複雑なビューを構成する基本的な要素であるスタック(UIStackView)、テーブル(UITableView)、コレクション(UICollectionView)、スクロール(UIScrollView)、タブ(UITabBar)の使い方を学ぶと、自分の思い描いたビューを実現するための下地が整うのではないでしょうか。

4.1. 複雑な設計を採用する前に考えたいこと

近年、iOSアプリ開発をする上でどのような設計手法を取るかというような話題をよくみかけます。「iOSクリーンアーキテクチャ」や「ヘキサゴナルアーキテクチャ」など熱心に議論が行われています。これらの議論や記事ではアプリの規模感についての前提が共有されていなケースが多く、場合によっては過剰な構成となり、コードの可読性や開発のスピードを下げてしまうこともあるでしょう。開発メンバー間で考え方も一致させていく必要があり、本当に必要なものが何なのかを見極めることが非常に重要です。

iOSアプリのコードベースは往々にしてそれほど大規模なものにはならず、だいたい1万〜2万行程度で構成されることが多いと思います。アプリ全体のアーキテクチャパターンを考えることは確かに大切なことですが、私自身はSOLID原則やDRY原則、YAGNI原則などのシンプルで基本的なプログラミング原則を意識してコーディングしていくように心がけています。アーキテクチャのたくさんの決まりごとを意識しつづけるのは難しいことです。少数のシンプルで明確な決まりごとを意識していると実は自ずと、本に書かれているようなアーキテクチャになっていることは多々ありますし、そもそも複雑なアーキテクチャの根底思想にはシンプルな原理原則があることが多いと思います。

4.2. ライブラリの選定

アプリのすべてのコードを自前で書くことは稀で、多くの場合オープンソースライブラリの助けを借りることになると思います。ライブラリをうまく使いこなせば大幅に開発期間を短縮することができ、場合によっては多くの人に使われたり、メンテナンスされているため、一人で書いたものに比べ品質の高いコードを利用することができるなどというメリットもあるでしょう。

ただし、Swiftのライブラリについては、今後予想される言語自体のアップデートに継続的に対応させる必要があります。過去に大きな変更が幾度も入ったため、GitHubでStarがたくさんついていて、多くの開発者が利用しているライブラリでも、最新の言語バージョンにアップデート対応がされず、放置されていることは珍しくありません。利用する前に、メンテナンスが継続して行われているかをGitHubのPulseなどで確認しておくことをおすすめします。また、メンテナンスが止まってしまった場合、公開リポジトリからフォークして、自分でメンテナンスしていく覚悟が必要です。

これはSwiftのライブラリに限ったことではないですが、基本的にはコードの8割程度をざっくりと斜め読みし、コードの設計・品質・メンテナビリティ・適切なテストが存在するかなどを確認してから依存を決めると失敗がないと思います。また利用した各ライブラリのライセンスを遵守し、必要であれば必ずライセンス表記をしましょう。

4.3. ライブラリへの依存

ライブラリを導入すると一口に言ってもいろいろなやり方があります。たとえば秘匿情報などを管理できるキーチェーンにアクセスするライブラリを使うことを想定してみましょう。何も考えずにViewControllerの必要な箇所でライブラリをimportして依存コードをばらまいていくというやり方は、最初の実装者にとっては一番手が抜けて楽かもしれません。しかしこれには大きな問題があります。ものにもよるとは思いますが、おそらくライブラリを利用している周辺のコードは似たような実装が繰り返し登場しているのではないでしょうか?またライブラリのインターフェースが変更されたり、利用するライブラリを差し替えたりする場合、あちこちに散らばったコードに手を加える必要がでてきます。

したがって、個人的にはライブラリを利用する際にはなるべく依存コードを記述する範囲を狭くするように意識することが多いです。大半の場合はひとつのクラスの中に閉じ込めることができるはずで、その上でプロトコルを切り、実装クラスを差し替えられるような構造にするのが好みです。こうすることによってライブラリを差し替えたり、自前の実装に切り替えたりするなどの対応も容易になります。またテストの書きやすさにも繋がる場合もあると思います。

5. アプリと非同期処理

ユーザーインターフェースの存在するアプリは常にユーザーからの入力を受け付ける状態を保たなければならないという制約があります。したがって、何か重たい処理をするときは別のスレッドに仕事を任せる必要があります。この重い処理にはどんなものがあるでしょうか。例えばサーバーにデータを取得する際はリクエストを送ってからレスポンスが帰ってくるまで時間がかかります。この間ユーザーの入力を一切受け付けないアプリは控えめにいって最悪でしょう。また画像の変換や複雑な計算などはユーザーからのアプリへの入力をブロックしてしまいます。

これを避けるためには、重たい処理はユーザーからの入力を受け付ける担当とは別のものに任せればよいでしょう。ユーザーからの入力を受け付けている窓口はメインスレッドとよばれています。対してその裏で処理を行なっているものをバックグラウンドスレッドとよんでいます。また、あるタスクの実行を止めずに別の処理を行うことを非同期処理と言います。iOSアプリ開発において非同期処理はスレッドを利用して実現されていますが、Swift, Objective-Cからは開発者が直接スレッドを触ることなく非同期処理を取り扱える仕組みが用意されています。それがGrand Central Dispatch(GCD)とオペレーションキューになります。それぞれどのようなものかざっくりと特徴をみていきます。また、Appleが公式で提供している「並列プログラミングガイド」および「スレッドプログラミングガイド」を参照するとコードレベルの技術詳細に迫ることができると思いますので、ご覧ください。

5.1. Grand Central Dispatch(GCD)

GCDを利用する場合もオペレーションキューを使う場合も、基本的にはキューにジョブを積むというのが基本的な実装内容になると思います。ではこの2つの何が違うのかというと、用意されているキューの種類とジョブをクロージャで渡すのか、オブジェクトで渡すのかという点になります。

GCDには、キューイングされた処理を逐次実行していく直列ディスパッチキュー(Serial Dispatch Queue)と並列に実行していくのが並列ディスパッチキュー(Concurrent Dispatch Queue)が用意されています。さらに特別なキューとしてメインスレッドで行いたいタスクをキューイングするためのメインディスパッチキューというものが用意されています。メインスレッドはひとつしかなく、並列にできないので当たり前ではあるのですが、これは構造的には直列ディスパッチキューになります。

さて、実際のアプリの中でこれらがどのように利用されるかを少し想像してみましょう。例えばボタンを押したイベントを皮切りにサーバーにHTTP通信を走らせ、その結果をテキストボックスに表示するケースを考えてみましょう。最初にメインスレッドがボタンの押下イベントを受け付け、イベントハンドラを呼び出します。続いて、イベントハンドラは並列ディスパッチキューにクロージャをわたして、あとの処理をバックグラウンドスレッドにお任せします。このように、裏側で行わせたい仕事をキューに積んで放置することにより、ユーザーからの入力を受け付けられる体制に即座に戻ります。一方、バックグラウンドスレッドではどのようなことが行われているでしょうか。並列ディスパッチキューに渡されたクロージャの中身をみてみましょう。まずHTTPリクエストをサーバーに送るでしょう。そしてレスポンスが帰ってきたら、そのデータをよしなに加工して、テキストボックスに反映させる必要があります。このときに注意が必要で、基本的にUIコンポーネントを更新する際はメインスレッドでやる必要があります。したがって、加工し終わったデータの準備ができたら、今度はメインスレッドに処理を行わせるための直列ディスパッチキュー(DispatchQueue.main)にクロージャでお仕事を渡します。お仕事内容は単純にデータをテキストボックスに反映させるだけです。

5.2. オレペーションキュー

オペレーションキューを用いる非同期処理では、タスクを表現したデータ構造であるOperationを作り、キューに渡していくという流れになります。したがってあらかじめタスクを仕込んでおいて一気に並列/直列で実行するなどということが可能です。実際のところGCDのラッパーでしかないため動作原理はほぼほぼ同じになります。しかし、クロージャではなくタスクというオブジェクトを引き回せることでより柔軟で複雑なプログラミングが可能になると思います。

5.3. スレッドセーフな実装

複数のスレッド間で共有されるオブジェクトは不正な状態になる可能性をはらんでいます。不変なオブジェクトであれば問題はないですが、状態を持つオブジェクトには不正な状態になることを防ぐための実装を施してあげる必要があります。一般にマルチスレッド間でのオブジェクト共有に対して安全であることをスレッドセーフな実装とよんでいます。スレッドセーフな実装を実現させるためのツールにはアトミック操作とメモリバリア、ロック、条件変数などがあります。それぞれ特徴が異なっており、用途に適したものを利用する必要があります。詳細についてはAppleが公式で提供している「スレッドプログラミングガイド」を読むと良いと思います。

6. アプリ内通信のこれから

多くのアプリではウェブAPIを利用したり、画像/音声/動画リソースをインターネット上から取得します。これからSwift/Objective-Cで開発をはじめようという方はどのようにしてHTTPリクエストを走らせるのかという点が気になるのではないでしょうか。

ネットで検索をするとAFNetworkingやAlamofireといったライブラリの名前がヒットすると思います。しかし個人的には標準で提供されているURLSessionで十分足りると考えています。複雑なPOSTリクエストの組み立てなどには一部ライブラリの助けがあると楽をすることができるかもしれませんが、それを除けばライブラリに実装されている機能を利用する機会はそれほど多くないでしょう。通信ライブラリに限らない話ですが、本当にそのライブラリを導入する必要があるのか、よく検討してみてください。

6.1. App Transport Security

App Transport Security(ATS)とはiOS9.0以降で導入されたサーバークライアント間でのセキュアな通信を保証するための仕組みです。Appleが推奨するTLSバージョンと暗号スイート、サーバー証明書とそのハッシュアルゴリズム、サーバー証明書の署名キーを満たしていない場合に接続エラーとなります。

2017年4月現在ではATSに対応することは必須ではなく、HTTP通信やAppleの推奨条件を満たさないHTTPS通信を行いたい場合は、Info.plistに設定を追加することでATSによる接続エラーを回避することができます。しかしながら、AppleはATSに正当な理由なく対応していないアプリをリジェクトするとの予告を出しており、今後アプリ開発をしていく上ではこれに対応することはほぼ間違いなく必須条件となるでしょう。

少なくとも自前で立てているサーバーと通信を行う場合は、Appleが定めた推奨条件に対応させましょう。ただし自分の管理外サーバーとの通信を行う場合は、ATSに対応しない正当な理由として認められるようです。

6.2. APIクライアントは人間の書くものではない

大半のアプリではREST APIを叩き、そのレスポンスをもとにビューを更新するなどの処理が入るのではないでしょうか。こんなときにはAPIクライアントの実装が必要になるでしょう。

残念ながらSwiftでAPIクライアントを書く作業はSwiftyではありません。URLSessionを用いてレスポンスを取得し、正常なリソースが得られたらJSONなりXMLなりをパースしてJSONObjectを手に入れます。続いてJSONObjectの各キーから値を取り出してSwiftで定義したエンティティの構造体にマッピングします。JavaやC#などを使うとシリアライズやデシリアライズの工程はだいたい手で書く必要はなく、十分に実績のあるライブラリが自動的にやってくれるため、とてもSwiftyなのですが、Swiftには現在そのような機能を実現するライブラリは存在しないようにみえます。またあらかじめスキーマが決まっているエンティティの構造体を書くのも非常に面倒臭い作業で自動化したい機運が高まります。

APIクライアントのレイヤーを自動で解決するための方法はいくつかあります。ひとつ目はSwaggerというREST API作成のためのフレームワークを利用する手法です。仕様を表現したYAMLファイルから自動でAPIクライアントのコードを生成できるのが特徴です。

またREST APIからは外れますが、Protocol Buffersを使うことによりデシリアライズやエンティティの作成を自動化できます。こちらはサーバー側から送出されるレスポンスもProtocol Buffersに対応させなければならないため、自前のサーバーを利用したアプリ作成のときに使える手段となります。サーバーとクライアント間の通信も含めて、すべて自動でコード生成したい場合はRPCフレームワークのgRPCの利用を検討してみてください。

7. ユーザーに笑顔を届けるまでがiOS開発

Appleによる過酷なダンジョンを切り抜けついに審査を切り抜けたみなさんは、ついにリリースボタンを押すことになるでしょう。いままで開発をすすめてきたメンバーと一緒にリリースボタンを押すとウェイ感がでてとてもエモいので是非おためしください。

7.1. AppStoreのねぼすけ

さて、時間はお昼の12時、無事リリースボタンを押した我々はAppStoreに飛び、アプリをダウンロードしてみようと試みます。しかしながら、アプリは見当たりません。Appleの実装はとてもlazy(怠惰)なのでリリースボタンを押してから反映までに30〜60分程度要すると思っていただいてよいでしょう。もし決まった時間にプレスリリースやSNSなどでリリース告知をする際には、あらかじめこっそりとアプリをリリースして、AppStoreに反映させておくのが良いと思います。

そんなこんなでアプリは13時にはストアに反映されていたとしましょう。ところがtwitterでエゴサするとダウンロードがうまくできないとの酷評が。リリースと同時につけられる1ツ星評価。実際のところ何が原因か分かっていないのですが、お昼の時間帯などはAppStoreからのダウンロードがうまくいかないときがあるようで、個人的にはリリースはアプリのユーザーがアクティブでなくなってくる時間帯にするようにしています。個人的にはAppStoreのアプリ配信サーバーを増強してほしいという気持ちをここに掲載させていただきます。

7.2. 俺たちの闘いはこれからだ!

アプリをリリースして、開発を終わらせるぞ!そう思っていた時期が私にもありました。しかしアプリ開発はリリースしてからが本番です。まずは多くのユーザーに使ってもらうためにきっと様々な仕掛けが必要でしょう。また発生させてしまった不具合の対応や、アプリをさらに快適に利用できるような新機能の提供などやることはたくさんあるはずです。そう、俺たちの闘いはこれからだ!

大規模ネイティブアプリへのプッシュ通知機能導入にあたって考えたこと

この記事は 「ドワンゴ Advent Calendar 2016」23日目の記事です。

 この記事は、比較的規模の大きなサービスのネイティブアプリにプッシュ通知機能を導入する風景を淡々と描いたものです。過度な期待はしないでください。なお、一応お約束として書いておくと、この記事は個人の見解であり、所属する組織の公式見解ではありません。

はじめに

 Amazon SNSFirebase などのプッシュ通知配信基盤のおかげで、iOS/Android 端末へのモバイルプッシュ通知の実装自体は簡単にできるようになってきました。これらのサービス基盤の利用にはサーバーサイドの知識はほぼ必要なく、普段ネイティブアプリ開発しかやっていないような方でも簡単に利用することができる作りになっています。しかしながら、実際に比較的大きなサービスにモバイルプッシュ通知機能を導入・運用していくにあたっては、地味に考慮しなければならない点がいくつも見つかりました。

 Amazon SNS を利用したプッシュ通知の実装方法自体については公式のドキュメントをはじめとして、Qiita にも記事がゴロゴロ転がっているのでお好きなものを参照していただくとして、この記事では大規模サービスのネイティブアプリにプッシュ通知機能を導入する際に考えなければならないことをまとめていくことにします。

どんなプッシュ通知機能が必要なのか

 プッシュ通知機能は諸刃の剣です。ユーザーに対してサービス側から好きなタイミングでメッセージを送ることができる一方で、不要な通知はユーザーの気分を害します。そしてそれは、通知をオフにされたり、アンインストールされたりといったことに繋がるでしょう。

 ネイティブアプリにプッシュ通知機能を導入する際にまず考えなければならないことは、「なんのためにどんなプッシュ通知を送るのか」を決めることです。非常にマジレス的でつまらない、当たり前の内容ですね。

 しかし、世の中には意味不明なプッシュ通知を大量に送ってくるアプリがあり、私も日頃アンインストールしまくっているので、こうした当たり前のことを検討していないか、ユーザーへのいやがらせのためにプッシュ通知を送って楽しんでいる悪趣味な方がたくさんいるような雰囲気を感じます。

機能要件の想定とプッシュ通知のパターン

 さて、とあるところに作品を投稿できるサービスがあり、ネイティブアプリから作品が閲覧できるシステムがあったと仮定しましょう。ユーザーはきっと、気に入った作品が更新されたら通知が欲しいのではないでしょうか。また、サービス内の突発的なイベントや面白い作品の情報などについても通知が欲しいというケースがあると思います。

event.png

プッシュ通知パターンの分類

ここで登場した2つの通知パターンについて詳しくみていくと、特徴が違います。

  1. 気に入った作品の更新情報通知(個別通知
  2. イベントなどの情報通知(全体通知

 前者は、ある作品をお気に入り登録しているユーザーにだけ通知がいく個別通知になります。一方後者は、任意のタイミングで全ユーザーに通知がいく全体通知になります。そして、個別通知は比較的定形のメッセージ、全体通知は自由なメッセージを送りたいケースが多いのではないでしょうか。まとめると以下の表のようになります。

種別 内容 送信先 通知発生タイミング メッセージ形式
個別通知 お気に入り作品の更新情報など 個別のユーザー サービス運営者による制御が難しい ほぼ定形
全体通知 イベントやお知らせなど 全ユーザー サービス運営者により制御できる 自由

 世の中にたくさんのアプリが存在しますが、実はどんなアプリも通知のパターンとしては、だいたいこのようなものに落ち着くのではないでしょうか。

通知パターンの分類から検討事項を洗い出す

送りたいプッシュ通知の内容を分類した結果をみると、いくつか検討が必要な項目が見当たります。それぞれについて見ていきましょう。

検討1:個別通知の発火タイミング

 ユーザーが作品を投稿できるようなサービスの場合、そのイベントの発生時刻をサービス運営者が制御することは不可能といって良いでしょう。これは、真夜中にユーザーのデバイスに通知が行ってしまう可能性があるということを意味します。

 こうした場合、ユーザーのデバイスにプッシュ通知が到達したあと、すぐにメッセージを表示せずに溜めておき(サイレントプッシュ通知)、定時にユーザーに知らせるという手法を検討すると良いでしょう。

一般的な方はアプリを

  1. 通勤・通学中(7:00〜9:00)
  2. お昼休み(12:00〜13:00)
  3. 学校や会社終わり(15:00〜18:00)
  4. 寝る前(21:00〜24:00)

などに使うことが多いのではないでしょうか。このあたりは各アプリのユーザーの特性によって大きくかわる部分ではありますが、いずれにせよユーザーがいつ通知を欲しがるかを検討することは必要かと思います。

 そして必要であれば、サービス運営者が制御できないイベント通知に関してサイレントプッシュ通知を利用し情報をデバイスにスタックしたのちに、ユーザーへ定時にメッセージを出すことを検討してみてください。

検討2:全体通知メッセージの内容や遷移先のテスト

 全体通知として、サービス運営者がメッセージを自由に入力し、アプリ内での遷移先を自由に定めることができる仕組みを作った場合に考慮しなければならないことは、オペレーティングミスです。人間が自由に操作できる部分が大きければ大きいほど、この手のミスは起こりやすいでしょう。また、プッシュ通知は一度送信してしまうと、取り消しは基本的にはできないため、ミスに対する取り返しはほぼつきません。手が滑って適当なメッセージを送ってしまえば、炎上の要因にもなりうるでしょう。

 こうしたことは、送信予定のメッセージをあらかじめサービス運営者のみに送信できる仕組みを作れば、ある程度防ぐことができるのではないでしょうか。サービスの規模や特性などにもよりますが、プッシュ通知の事故のリスクとテストメッセージ送信の仕組みを天秤にかけ、必要かどうか判断することになるかと思います。

機能要件まとめ

以上のことを検討した結果、次のような機能を実装する必要があると仮定しましょう。

  • 特定ユーザーへイベントごとの定形メッセージを通知する機能(個別通知)
    • アプリ側でメッセージの出現タイミングを制御する機能(サイレントプッシュ+定時通知)
  • 全ユーザーに対し、自由なメッセージや遷移先を指定した通知を送る機能(全体通知)
    • 事前にサービス運営者にのみにテスト送信・動作確認できる機能(通知テスト機能)

以降はこれらの機能を Amazon SNS を用いて構成していくことについて考えていきます。

プッシュ通知機能をどのように実装するのか

 いよいよプッシュ通知システムの実装方法について検討していきます。ここまで技術的な話があまりなかったので、ただのポエムに成り下がるところでした。危ない…。

Amazon SNS モバイルプッシュ通知概論

 Amazon SNS を用いて iOS/Android デバイスにプッシュ通知を送るためには次のような手順を踏む必要があります。初見の方にはよく分からない単語が出てくるかと思いますが、ひとまず流れだけを見てみてください。

prepare (4).png

SNS を利用したモバイルプッシュ通知のための準備

 図を見ながら順を追って説明していくと、まずアプリ起動時に iOS アプリであれば APNS に、 Android アプリであれば GCM にデバイストークンを払い出させます。これは(各アプリごとに)デバイスを一意に特定するためのトークンです。

 続いてそのトークンをサービスのAPIサーバーなどへ送出します。この部分についてはアプリにAWS SDKを入れてしまい、APIサーバーを通さない実装もできるかと思います。しかし個人的には、プッシュ配信基盤をいつでも差し替えられるようにしておきたいので、デバイストークンをどう処理するかについてサーバー側が握れる仕組みの方が好みです。

 APIサーバーは、受け取ったデバイストークンを Amazon SNS 上に作った application へ登録し、払い出された endpoint_arn を受け取ります。application はプッシュ証明書等と紐付いた概念であるため、iOS/Android でそれぞれ別のものを作成する必要があります。また、application には通知を送りたい対象のデバイストークンを登録する必要があります。登録を行うと endpoint_arn というものが発行されます。これはデバイストークンと1対1に紐付いているものになります。デバイストークンのフォーマットは iOS/Android で異なるため、 AWS 側で扱いやすいように統一的な名前を発行しているのではないかと思います(※個人の感想です)。こうして払い出された endpoint_arn と デバイストークンはユーザー情報と紐付けて永続化しておきます。

 個別通知を送るだけであれば、application にデバイストークンを登録し、endpoint_arn を払い出すところまでで十分なのですが、全体通知をするために、今回は Amazon SNS のトピックという機能を使うこととしましょう。トピックは subscriber を要素として保持しており、トピック宛てにメッセージを送ると subscriber 全員にメッセージを配信してくれるという機能を持っています。したがって作成したトピックを、払い出されたすべての endpoint_arnsubscribe させれば、全体通知を実現することができます。

SNS を利用したモバイルプッシュ通知の流れ

 以上が Amazon SNS を利用したモバイルプッシュ通知のための下準備になります。続いて実際に通知を送信するときの流れについてざっくりと見ていきましょう。

prepare (5).png

 図のように、個別通知の場合は DB からメッセージを送りたい対象ユーザーの endpoint_arn を拾い、そこ宛てにメッセージを通知します。全体通知の場合はトピックに対してメッセージを通知します。すると Amazon SNS はAPNSやGCMなどの各プラットフォームの通知配信サービスへとメッセージを届けてくれます。そこから先は APNS や GCM がよしなに各ユーザーのデバイスへの通知を届けてくれるでしょう。

 ひとつ注意が必要なのは、デバイストークンは一定期間を経ると無効になるということです。無効になるタイミングは APNS/GCM 次第であるため、アプリ起動時には毎回デバイストークンをサーバーサイドに送り、endpoint_arn を払い出すようにする必要があります。また無効になったデバイストークンに紐づく endpoint_arn へメッセージをを送ると当然通知は失敗するのですが、その際に endpoint_arnが持つ属性である Enabledfalse になった状態で、 application に残り続けます。放置し続けてもよいのですが、たまり続けると実装方法によっては個別通知のパフォーマンスが低下したりなど問題が起こりうるので、なんらかの削除機構を作るのがよいのではないでしょうか。

以上が Amazon SNS を利用したモバイルプッシュ通知のざっくりとした流れになります。

実装するシステムの全体像

 Amazon SNS を用いたモバイルプッシュに関する基礎知識を確認したところで、前節で想定した次のような機能要件を実現するシステム構成の全体像を見てみましょう。ただし、サービスは基本的にオンプレミスで動いていることを前提とします。

機能要件

  • 特定ユーザーへイベントごとの定形メッセージを通知する機能(個別通知)
    • アプリ側でメッセージの出現タイミングを制御する機能(サイレントプッシュ+定時通知)
  • 全ユーザーに対し、自由なメッセージや遷移先を指定した通知を送る機能(全体通知)
    • 事前にサービス運営者にのみにテスト送信・動作確認できる機能(通知テスト機能)

user (3).png

 ご覧のように、結構複雑になります。すべてAWS上に乗っかっていたり、Direct Connectなどを利用しているのであれば、もう少しシンプルな作りにはできる部分はあるかと思います。あと、図中には登場していないのですが、ネイティブアプリ側のプッシュ通知処理実装はもちろん必須になります。

サーバーサイド各モジュールの実装

図中に登場した 6 つのモジュールの実装についてそれぞれ見ていきます。

1. デバイストークン受け取り用のエンドポイント作成

 ネイティブアプリからデバイストークンを受け取るエンドポイントを作成します。適当なパスを切り PUT で受け取るような形になるかと思います。サーバーはデバイストークンを受けとったあと、その処理は他のジョブに任せてさっさとレスポンスを返してあげましょう。そのためにキューにデバイストークンとユーザーIDとプラットフォームタイプ(iOS/Androidなど)の情報を含んだジョブを登録します。

 オンプレ環境にすでにジョブキューがあるのであればそれを使い、なければ Amazon SQS の料金が激安なので利用してみるのも手かと思います。

2. デバイストークン処理用のバッチジョブ作成

 ユーザーから受け取ったデバイストークンの処理を受け持つバッチジョブを作ります。このジョブがしなければならないことは以下の4つになります。

  1. 各プラットフォーム(iOS/Android)に対応する application にデバイストークンを登録し、endpoint_arn を払い出させる
  2. 全体通知用トピック を endpoint_arnsubscribe させる
  3. もし、全体通知テストユーザーだったらテスト用トピックも subscribe させる
  4. device_tokenendpoint_arn をユーザー情報に紐付けて永続化させる

 1. の手順については Amazon SNS の CreatePlatformEndpoint という操作を行うことになります。また、2.3. の手順については Subscribe という操作を行うことになります。詳細についてはドキュメントを確認してみてください。

3. 作品更新時にジョブをキューに積む

 既存の稼働しているサービスへの作品更新時に、プッシュ通知送信ジョブを作成し、キューに積んであげるように変更をいれましょう。この部分はきっとほんの少しの実装で済むのではないでしょうか。

4. 作品更新プッシュ通知ジョブ作成

 このジョブは、作品をお気に入りに登録しているユーザーの endpoint_arn のリストを取得し、ペイロードを並列で publish します。これには Amazon SNS の Publish という操作を行います。個人的にはこの Publish の宛先である endpoint_arn をリストで指定できるように AWS 側に機能追加してほしさはあるのですが、いまのところ 1 回の publish で 1 つの endpoint_arn しか指定できません。今後に期待しています…。

5. 全体通知送信フォームをサービス管理ツールに追加する

 ある程度の規模以上のサービスであればきっと、サービスを管理するツールは存在すると思います。これまで見てきたように作品更新通知についてはバッチジョブ連携により自動的に送信される仕組みになっています。しかし、イベント情報などを知らせる全体通知に関しては、サービス運営者の手によってメッセージや遷移先の指定をする必要があるため、サービス管理ツールにフォームを追加する必要があるでしょう。

 オペミスを防ぐために、最初にテスト通知を送り、確認したものしか本番送信できないような仕組みづくりなど、工夫次第で手間のかかりぐあいはだいぶかわる箇所でもあります。

 また通知の即時送信の仕組みしかないと、サービス運営者は決まった時間に送信ボタンを押さなければならず、運用コストは大きくなりそうです。必要であれば通知を予約する仕組みなどを作ると良いのではないでしょうか。

6. disabled なエンドポイントを削除するジョブ

 最後に無効になったエンドポイントを削除するジョブを実装しておきましょう。Amazon SNS の application が持つ endpoint_arn には、その属性が変化したときに SNS トピックに通知してくれる機能があります。 application の設定画面の以下のような部分に通知先の SNS トピックの ARN を設定する項目があるはずなので、そこを開いて disabled なエンドポイント削除トリガー用トピックのARNを指定してあげてください。

0e022af5-654a-24ad-f2d1-b5c7e9a92258.png

 続いて実際に削除処理を行うラムダ関数を用意しておきましょう。こちらはラムダ関数に飛んできたデータの中から endpoint_arn を抜き出し DeleteEndpoint という操作をすればOKですので、適当にラムダ関数を実装してください。

 ラムダ関数の実装が終わったら、 disabled なエンドポイント削除トリガー用トピックに、作ったラムダ関数を subscribe させれば、 disabled になった瞬間にラムダ関数が走り、エンドポイントが削除されます。大きなサービスで全ユーザーにプッシュ通知を送ったりすると、当然無効なエンドポイントもたくさん生まれるので、もしかすると AWS Lambda の同時実行制限に引っかかってしまうかもしれません。そのあたりは調整して、場合によっては上限緩和申請を行うとよいでしょう。

おわりに

 クラウドサービスが手頃な値段で誰でも簡単に利用できるようになってきた今、「アプリ開発エンジニア」や「サーバーサイドエンジニア」といった肩書きは意味をなさなくなってきています。思い立ったらすぐに iOS/Android アプリ開発者が API やプッシュ通知などサーバーサイドの機能に手を加え、運用していくことができるようになってきました。

 しかしながら、それなりの規模のサービスに導入しようとなると、まだまだ検討しなくてはいけないことが多いということも事実でした。今後ますますクラウドサービスが発展していっても、人間が考える余地は残り続けるはずなので、クライアント/サーバーの知識をバランスよく摂取して、ユーザーに快適に利用していただけるサービスづくりをこれからも進めていきたいですね。

 なお、今回検討した内容の一部は、この秋開催されたAWSのカンファレンス Re:invent にて発表された Amazon Pinpoint という新機能を用いるとよりきれいに実装できたり、もっと高機能にできる可能性があります。このあたりについては引き続き調査を行い、今後の改善につなげていきたいと考えています。

謝辞

 偉そうに記事を書いてますが、以上のことは自分一人で考えたわけではないので、一緒にお仕事をしたチームの方々に感謝の気持ちを記しておきます。今年も一年間、楽しい開発ライフをありがとうございました。

以上、ドワンゴ Advent Calendar 2016 23日目の記事でした。明日は @kwappa さんの記事です。楽しみにしております。