タグ: Swift

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. 俺たちの闘いはこれからだ!

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

iOSアプリ開発基礎ハンズオン

この資料はラビットハウス社内で開催される、iOSアプリ開発未経験者向けのハンズオン会向けに作成されたものです。このハンズオンを一通り行うことにより、iOS開発において以下のことができるようになります。

  • 基本的なUIKitコンポーネントの利用
    • UILabel: テキストの表示
    • UIButton: ボタンの表示
    • UIBarButtonItem: バーボタンの表示
    • UITextField: テキストフィールドの表示
    • UITableView: テーブルの表示
    • UITableViewCell: テーブルセルのカスタマイズ
    • StoryboardとAuto Layoutの利用: UIをデザインベースで構成する仕組み
    • プッシュ遷移とモーダル遷移: iOSの大きな2つの画面遷移パターンへの理解

お品書き

0. はじめに

 iOSアプリ開発自体は技術的にはそれほど難しくないため、他の分野のエンジニアに開発に関わってもらえるようにハンズオン会を開催しました。また、場合によっては、簡単なプログラミングができるアプリディレクターやデザイナーにも理解出来るようになるべく平易な内容とするように心がけました。 if, else などのキーワードや変数、関数などについてざっくりと理解していて、簡単な関数を実装できれば、この資料を見ながらきっと誰でもアプリ開発ができると思います。

 2時間でTODOアプリを作るところをゴールとして進めていきますが、プログラミングに慣れている方であれば1時間で終わってしまう内容かもしれません。しかしながら、iOSアプリ開発のフローは一通り体験でき、Googleで検索をしながらアプリ開発を進めていけるレベルには達するのではないかと思います。また、このハンズオンで実装を進めていくアプリのソースコードは https://github.com/53ningen/iOSHandsOn に置いてありますので、困ったときは参照してみると良いかもしれません。

それでは、iOSアプリ開発の世界に飛び込んでいきましょう。

1. 環境構築

このハンズオンを進める上で必要な Xcode 8.2 の実行環境を構築します

1.1 Xcode 8.2 の導入

 Xcode 8.2 を含めた過去のバージョンの Xcode は以下の URL からダウンロードすることができます。ダウンロード、およびインストールにはかなりの時間がかかりますので、時間に余裕のあるときにやることを強くお勧めします。

https://developer.apple.com/download/more/

1.2 .gitignore

自分がよくつかってる .gitignore は以下のような感じです

2. プロジェクトの作成、 Hello, world とその周辺

 ここでは実際に開発を進めるためのプロジェクトを作成し、簡単な文字を表示させるところまでを行います。

2.1 プロジェクトの作成

Xcode を起動し、 Create a new Xcode project を選択してください。

xcode

続いて Single View Application を選択してください。

ChooseATemplateForYourNewProject

 ProductName として、とりあえず今回のハンズオンでは iOSHandsOn を指定しておきましょう。また、Organization Name は適当に、Organization Id についてはサービスのドメイン名や、github の id を指定することが多いかもしれません。

 このあたりの指定については実際にプロダクトを AppStore へリリースするなどの際に重要になってきますが、今回のハンズオンアプリではとりあえずの値を指定しておけば良いでしょう。また、後から変更することも可能ですので、必要であればそのときに直せば良いと思います。

ChooseOptionsForYourNewProject

 以上をすすめると最後にプロジェクトを保存するパスを指定するダイアログが出てきますので、お好きな場所に保存すると良いでしょう。これでひとまず iOS アプリ開発のためのひな形を作成することができました。この状態で Command + R (アプリ実行へのショートカットキー) を押すと、iOSシミュレーターが立ち上がり、真っ白な画面が表示されると思います。

2.2 Hello, world

続いて簡単なサンプルとして画面に文字を表示させてみましょう。目標とするイメージは下図のようなものです。

 早速 Main.storyboard を選択し、右下にあるパネルからラベル(UILabel)を選択して上図のような画面を作ってみてください。ラベルに表示される文字はラベルをダブルクリックすると編集できるようになります。作業が終わったら再びシミュレーターで実行してみてください。きっとストーリーボードに入力した文字どおりの表示が出ているかと思います。しかしながら、このままでは画面回転をするときっとレイアウトが崩れてしまうでしょう。試しに Command + → キーを押して画面を回転させてみてください。

 画面が回転した場合でも上下中央位置に表示させたい場合にはConstraints(制約)を指定してあげる必要があります。ラベルを選択したあと、ストーリーボード編集画面右下のボタンを押し、下図のような設定にチェックを入れ Add 2 Constraints ボタンを押してください。それぞれ Horizontally in Container は水平方向中央揃え、Vertically in Container は上下方向中央揃えの意味となります。最後に右下5つアイコンがならんでいる部分の一番左、更新マークのボタンを押すと、追加された制約がストーリーボードに反映されます。

 この状態で再びシミュレーターを立ち上げ画面を回転させてみてください。きっと画面がどのような方向を向いていてもラベルは画面上下中央位置に表示されたのではないでしょうか。もしストーリーボードを使わずコードでUIを構成する際も同じような形で Constrain を指示してあげないと、画面回転時や異なる縦横比のデバイスでの表示がおかしくなることがありますので、デバッグ時に画面を回転させながらデザインが崩れないかをチェックしてみてください。

まとめ
* 開発中はこまめに画面回転を試し、制約のつけ忘れなどミスがないかを確認しながらすすめる

2.3 ボタンを押したら文字がかわる機能をつける

 画面にただ単に Hello, world と表示されているだけではつまらないので、ボタンを設置して、押したときの時刻が表示される機能を搭載してみましょう。まずはストーリーボードに対して下図のようにボタンを追加してください。

 続いてXcodeの右上にある ボタン(真ん中)を押してみてください。ストーリーボードとソースコードの2画面表示になったかと思います。この状態でラベルを選択したのち control キーを押しながらソースコードの適当な位置にドラッグすると下図のようなダイアログが出てくるでしょう。

 Name に label と入力し Enter を押すと @IBOutlet weak var label: UILabel! というコードが追加されると思います。これはストーリーボード上にあるラベルをコードから操作できるようにリンクしてあげたと思っていただければ大体良いと思います。ボタンに関しても同様に操作を行うと @IBOutlet weak var button: UIButton! というコードが追加されるでしょう。

 続いて、ボタンを押したときのイベント処理を書いてみましょう。 ViewController.swift を開いて、次のようなメソッドを追加してください。

 最後にボタンのタップイベント発生時にこのメソッドを呼び出すように設定するコードを追加します。

 これでボタンが .touchUpInside、つまりタップして指を離した瞬間に、先ほど定義したメソッドを呼び出すという紐付けができました。早速シミュレーターで動かして試してみてください。おそらく、ボタンを押すと現在時刻が表示されるようになったのではないでしょうか?

 ついでに、同じ仕組みを異なる方法で実装しておきましょう。一旦ストーリーボードのボタンを control キーを押しながらクリックして、New Referencing Outlet に紐付いているものを削除し、もう一度 control キーを押しながらソースコードへドラッグします。その際に下記のように ConnectionAction に変更します。この場合 Name の欄にはボタンの名前ではなく action の名前を指定することになるので buttonOnTouchUpInside など適当な名前を指定してあげてください。すると今度はメソッド定義が生成されたのではないでしょうか。この手法ではこうして生成されたメソッド内に、ボタンが押されたときの挙動を定義していくことになります。

さきほどと同じ内容を実現しようとすると最終的にコードは次のようになるかと思います。

 実装が終わったらシミュレーターで実行してちゃんと動作するかを確認してください。これで iOS で UI を構成する最も基本的なUI部品である UILabelUIButton の使い方の雰囲気は理解できたのではないでしょうか。

まとめ
* ボタンのイベント処理には @IBOutlet を使う方法と UIButton.addTarget を使う方法がある

2.4 トラブルシューティング

 この先ますます複雑なことをやるとしばしば謎のエラーがでて、うまくシミュレーターで実行できない場合がでてくるかと思います。そんな際は command + shift + alt + K キーを押して、ビルドした成果物をディレクトリごと消すということを試してみてください(mvn clean的なものになります)。また Xcode がおかしな挙動を見せてくることがしばしばありますが、たいてい再起動すれば治るかと思います。

 何か問題が生じた場合は無理に頑張ろうとせず、まず上記の2点を試してみるということを忘れずに、寛容な心で開発を進める必要があります。

3. 2種類の画面遷移

 1画面だけで構成されるアプリはほとんどなく、プロダクトとして出すものは、ほとんどの場合画面遷移が発生します。iOSの画面遷移には主に次の2つのパターンが存在します。

  1. モーダル遷移(下からビューが飛び出してくる、閉じるボタンでビューを閉じる)
  2. プッシュ遷移(右方向にビューがスタックする、スワイプで戻れる)

 iOSヒューマンインターフェースガイドラインにこの2つの遷移の使い分けの思想について書かれているので、Apple 信者の方々は是非そちらをごらんください。そうでない場合には普段のiOSアプリの利用の仕方に照らし合わせて適切な方を選んでいくと良いでしょう。例えばコンテンツの構造として 「漫画アプリ」 ⊃ 「漫画作品」 ⊃ 「漫画エピソード」 という図式が成り立つのであれば、きっと漫画アプリトップから作品への遷移と作品からエピソードへの遷移はプッシュ遷移が適しているはずです。また、この間の任意の場所でログインさせるというアクションをユーザーにさせたい場合はモーダル遷移でログイン画面をユーザーに提示し、ログインが完了したらその画面を閉じてあげると良いでしょう。

 余談になりますが、近年はデバイス自体が大型化してきており、モーダル遷移時の「閉じる」ボタンの位置が左上にあると手の大きさ的に届きにくい位置になるため、モーダルで出すビューの画面デザインをする際にこのあたりのことを気をつけると良いと思います(※ 個人の感想です)。前話が長くなってきたのでこのあたりにして、早速それぞれの遷移を実装してみましょう。

3.1 プッシュ遷移の実装

 プッシュ遷移をするためには現在の ViewControllerUINavigationController に属している必要があります(正確にいうと UINavigationControllerchildViewControllers の要素である必要ということ)。こんなこと言葉で言われてもわからないと思うので、早速作業をしながら理解していきましょう。

 ここではボタンを押すとプッシュ遷移をする仕組みを実装していきたいので、その準備段階として Main.storyboard でいままで「時刻を表示」となっていた部分を「遷移する」というテキストに変えてみましょう。続いて、ViewControllerを選択し、右カラムのメニュー Identity -> Storyboard ID の部分に Main というテキストを入力しましょう。

 そして「上部メニュー:Editor」→「Embed in」→「Navigation Controller」を選択してください。すると Navigation Controller とかかれたよく分からない画面が出現したと思います。と、同時にさきほどまでのビューの上部にも謎の灰色のスペースが生じてしまいました。とりあえず起動して動作確認してみましょう。

 上図のような状態になっているのではないでしょうか。さて、ここからプッシュ遷移を実装をしていきます。新しい画面を作るのは一旦後回しにして、まずは同じ画面に遷移するコードを書き足してみましょう。そのためにボタンのタップイベント処理のメソッド内を次のように書き換えます。

 この状態でシミュレーターで実行すると、ボタンを押したときにきちんとプッシュ遷移できるようになっているのではないでしょうか? またスワイプや左上の < ボタンで前の画面に戻れるようになっていると思います。

まとめ
* プッシュ遷移には navigationController?.pushViewController を使う

3.1.1 (プログラマ向けトピック) UINavigationController のふるまい

 UINavigationController の振る舞いについてちょっとだけ見ておきましょう。冒頭で UINavigationController は UIViewController をスタックしていくことが役割だと言いました。その様子を実際に見るためにページ遷移が終わった瞬間(viewDidAppear)に navigationControllerchildViewControllers をログ出力するコードを ViewController に仕込みます。

この状態でシミュレーター実行をし、遷移をしたり戻りながら、ログ出力を観察してみましょう。

きちんとスタック構造になっていることがわかるかと思います。

3.1.2 (プログラマ向けトピック) Storyboard と 型

 静的型付け言語になれたプログラマであれば、ViewController を取得するときのコードについて文字列で指定しているあたりに恐怖心を覚える方もいるのではないでしょうか? 実際この部分の指定を間違えると、静的チェックが走らず実行時にアプリがクラッシュします。

 この部分に対する対応策は各自で取らざるを得ないのが現状で、例えば次のようなやり方をしている人が多いです。

  1. 動作確認を徹底することによる対応(筋肉系)
  2. ViewController を生成するファクトリを作り、そのファクトリのすべてのメソッドについてテストを書き、確実にインスタンスが得られることを保証する
  3. ViewControllerStoryboard ID(または Storyboard のファイル名) の命名規則を定める、ViewController の型を受け取り、そのインスタンスを生成するファクトリメソッドを用意し、アプリ内ではそれを通してインスタンスを得る

 3. については具体的には、MainViewController に対する Storyboard の名前は必ず MainViewController にするという規則を設け、次のように ViewController の型名を取得して上記のようなことを実現するという手法になります。命名をミスれば安全でもなんでもない手法ですが、StoryboardやViewControllerの名前を変更することはまれであるため、手軽な方法ではないかと考えています。

3.1.3 (プログラマ向けトピック) Storyboard ID を使わず Storyboard に紐づくViewController を取得する

以下のようなコードで可能です。口頭で説明いたします。

3.1.4 プッシュ遷移画面から戻るボタンの実装

 プッシュ遷移をしたあとにスワイプで前の画面に戻れますが、ある条件のときに強制的に前の画面に戻したいことがあるかと思います。ここでは、ボタンを押すと前の画面に戻る機能を実装してみましょう。まずはおなじみ Main.storyboard を次のような状態に改変しましょう。またトラブル防止のため、各UIButton を control + クリックし Outlet を一旦クリアにしましょう。

 続いて、ストーリーボード上のボタンとコードを結びつける作業をもう一度やりましょう。その作業が終わったら、まず「プッシュ遷移する」ボタンについて以前やったとおりに addTarget を利用してプッシュ遷移の実装を行ってください。これがおわるとコードは以下のような状態になっているかと思います。

 つづいてポップボタンについてですが、こちらについては最初の画面では戻り先が存在しないのでボタンを無効化しておくのが親切だと思います。そのためにビューが読み込みされた時点(viewDidLoad)で navigationController!.childViewControllers.count (スタックされているViewControllerの数) が 1 より大きかったら有効にしてあげる処理を記述します。

 また、プッシュ遷移から戻る処理は navigationController?.popViewController(animated: true) という命令を呼び出してあげれば実現できるため、この処理とボタンのタップイベントを addTarget で紐付けましょう。

 シミュレーターで実行すれば期待したとおりの動作になっているのではないでしょうか。

まとめ
* プッシュ遷移からもどるときには navigationController?.popViewController を使う

3.2 モーダル遷移

 同様のノリでモーダル遷移についても実装していきましょう。モーダル遷移については UINavigationController うんぬんは一切関係なく、どのビューからもこの遷移が使えます。いつもどおり Main.storyboard に「モーダル遷移する」ボタンを、これまでの流れと同じ手順で一つ増やし、右上にモーダル画面を閉じるボタンを設置しましょう。イメージは下図のとおりです。

 例によって最初の画面では戻り先の画面が存在しないので、右上の × ボタンを無効化したくなります。モーダル遷移からの戻り先があるかどうかの判定には presentingViewController が使えます。これの中身が空っぽだった場合、戻り先がないということになります。諸々の実装は以下の通りになります。

まとめ
* モーダル遷移には present を使う
* モーダル遷移後の画面から元の画面に戻るには dismiss を使う
* モーダル遷移の戻り先の VC は presentingViewController に格納されている

4 TODO管理アプリの作成

 UITableView(テーブルビュー)はiOSアプリでしばしば見かけるUIコンポーネントのひとつです。たとえば標準のメールアプリや設定画面などに使われています。さて、ここからは簡単なTODOリスト管理アプリを作っていきましょう。TODOリストの表示にはテーブルビューが適していると思いますので、さっそく実装方法を見ていきましょう。

4.1 新しい画面の作成

 Storyboard に新たな ViewController を追加するほうほうもありますが、ここでは新しいストーリーボードと新しいViewControllerファイルを追加しましょう。TODOMainViewController.storyboardTODOMainViewController.swift を作成して以下のように画面を構成して、遷移できる状態まで持って行ってください。

 TODOMainViewController.swift のソースコードは以下のような状態になっていると良いかと思います。

これでTODO機能を実装する下地が整いました。

4.2 テーブルビューを組み込んでみる

 テーブルビューを組み込んでみましょう。TODOMainViewController.storyboardTableViewを追加して適切な constrain を設定しましょう。左・右・下方向への制約は constrain to margins のチェックボックスを外す必要がある点に注意してください。またViewController自体について Under Top Bars のチェックボックスをオフにしてください。TableViewのStyleは Grouped を選択します。

 つづいて TableView をコードに紐付ける作業を行ったあと、以下のようにコードを記述してください。tableView.dataSource はテーブルのデータを供給するための設定でここには、UITableViewDataSource プロトコルを満たす全てのインスタンスを指定することができます。また、tableView.delegate はテーブルのふるまいを指定するための設定でここには UITableViewDelegate プロトコルを満たす全てのインスタンスを指定することができます。

 
 ここで罠になるのが tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) です。ここに 0 を指定するとなぜかテーブルビュー上部に大きな余白ができます。なので、雑に対応したければ、0.1などの小さな値を指定するとよいでしょう。厳密にやりたければここに任意の数字を指定して、tableView自体を上方向にその任意の数字分ずらしてあげれば良いと思います(ホントか)。

4.3 テーブルビューのセルをスワイプできるアレ

テーブルビューのセルをスワイプできるアレを実現したいときには以下のようなコードを extension TODOMainViewController: UITableViewDelegate に追加します。

4.4 テーブルビューセルの表示を作る

 画面を構成する部品を作るには Storyboard ではなく、 xib というファイルを作ったほうが良いことが多いです(※あまりこだわりはないし、宗教的な問題だと思うので思想の強い方はブコメとかで暴れていただければ)。さっそくタスク名を表示するだけのセルを作ってみましょう。iOSHandsOn 直下に TODOMainViewTableCell.xibTODOMainViewTableCell.swift というファイルを作り、xibにViewを紐付けてください。このあとの作業はストーリーボードでやったときと一緒なので、同じノリでやって下図のような状態にしてみてください(だんだん説明が雑になってきた)。

4.5 「タスクを追加するテキストフィールド」を上部に適当に作る

もうだいたい絵を見れば実装する方法わかってきましたよね? TODOMainViewController.storyboard を以下の状態に変更して、コードベースとの紐付けもよしなにやってください(雑)。

4.6 TODOデータの入力・保存・削除の仕組みをひと通り実装する

 TODOデータを保存する仕組みとしてとりあえず、文字列のリスト([String], Array<String>) を使いましょう。注意して欲しいのは、この実装ではTODO管理機能のモーダルを dismiss するとすべてのデータが水の泡になるという点です。ちゃんとデータを保持するためのほうほうは後ほどご紹介しますが、基本的にはここで完成させたものに少し手をいれて、iOSデバイス内のファイルに保存するという形になりますので大筋の流れは同じです。というわけで、まず TODOMainViewController に以下のコードを追加しましょう。

 また、表示させるセルの数はタスクの数と同じにしたいので tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) を変更する必要があります。セルにはタスク名を表示させたいので tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) にも手を入れます

4.6.1 タスクの削除

 削除については対象のセルのインデックスと変数 tasks のインデックスが一致しているので、変数からタスクを削除してテーブルの再描画命令を呼び出してあげればよいので簡単です。削除はセルをスワイプすると出てくるボタンで実現するので、tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) に手を入れます。

シミュレータで実行すると、もうタスク削除機能がうまく動作していることがわかるのではないでしょうか?

4.6.2 タスクの追加

 テキストフィールドに文字を入力して、エンターを押した瞬間にタスクが追加されるという仕様を実装に落とし込みましょう。テキストフィールドの振る舞いは UITextFieldDelegate プロトコルを実装したクラスのインスタンスを指定することができます。

また普通のアプリだとキーボードで文字入力中にテキストフィールド以外の適当な部分をタップすると、キーボードを閉じて入力を中断することができます。その振る舞いを実装するには次のようなコードを追記すればよいと思います。

 まあこのままじゃテーブルビュー領域をタップした時にキーボードを閉じないんですけどね…。

4.6.3 ローカルへのデータ保存

あとで書く

4.7 テーブルビューのよくある実装

 テーブルビューを実装するときによく使われるのが「引っ張って更新」と「セルの逐次読み込み」なので、それぞれどのように実装されているのかを軽くおさらいしておきます。

4.7.1 引っ張って更新の実装

説明あとで書く

さりげなく非同期処理の導入をしてしまった

4.7.2 ページネーションの実装

あとで書く

5. CocoaPods の導入

 iOS開発にはしばしば依存ライブラリ管理ツールとして CocoaPods が利用されています。これは node における npm や Java における maven などのようなものです。CocoaPods は Ruby で作られていて、Mac には標準で Ruby の実行環境が入っていますが CocoaPods の最新バージョンは古い Mac に標準で入っている Ruby では動作しない可能性があります。

 そこでまず Ruby のバージョン管理ツール rbenv を導入しましょう。続いて Ruby のライブラリ管理ツールである bundler を導入し、それを利用して CocoaPods を導入します。よく分からないと思った方は、とりあえずおまじないだと思って以下のコマンドを実行していってみてください。余談ですがチーム開発においては、手元の環境で CocoaPods が正常に動くとしても新しいメンバーが開発に加わりやすいように rbenv を導入しておくと何かとスムーズで良いと思います。1

5.1 rbenv を使った Ruby 2.3.3 の導入

 rbenv とは、複数のRubyバージョンを切り替えることができるツールになります。マシンによっては OS X 標準で入っている Ruby のバージョンが古く、iOSアプリ開発によく使われる Ruby のライブラリ(gem)がうまく利用できない場合があるため、rbenv を利用して特定環境かにおけるつまづきどころをなくす意図があります。

5.2 bundler と CocoaPods の導入

プロジェクトルートに Gemfile を以下のように追加して rbenv exec bundle install を実行してください

続いて rbenv exec bundle exec pod initPodfile が生成されるので、いい感じに依存を指定して、 rbenv exec bundle exec pod install すれば良いと思います。

6. 追加課題

  • データ構造の定義(リスト/遅延リスト/スタック/キュー)
    • ユニットテストを書く
  • 電卓アプリの作成
    • その1: ボタンベースのiOS標準の電卓を目標に進める
    • その2: テキストフィールドベースでユーザーに数式を入力させ、計算結果を表示するアプリ
  • 本格的な TODO アプリを作成してください
    • タスクをどういった形で持てば拡張性を維持できる?
    • タスクがマイルストンやタグといったものに紐づくときにはどうすればよい?
    • データやロジックと UI がなるべくひっつかないようにするためにはどうしたらよい?
  • 株価/天気アプリの作成

時間が余ったら

  • URLSession を使った HTTP 通信
  • DispatchQueue を使った非同期処理
  • RxSwift を用いた非同期処理

などを試してみてください。


  1. rbenv を利用した開発フローに載せてくださった @koki_cheese さん、ありがとうございます 

UnicodeScalar とは?

docs: https://developer.apple.com/reference/swift/unicodescalar

The UnicodeScalar type, representing a single Unicode scalar value, is the element type of a string’s unicodeScalars collection.

UnicodeScalar は Unicodeスカラ値を表す値

UnicodeScalar から String への変換

Swift3について色々まとめていくスレ

String を Data に変換する

色々実験したスクショ

スクリーンショット 2016-08-12 18.50.49

Swift1系脳で出来ないと思い込んでいたことができるようになっていた件

再帰的構造を持つ enum

Swift 1 系では、再帰的構造を持つ enum を定義することはできませんでした。たとえば、

というコードを書くと、Swiftの処理系は受け付けてくれません。しかしながら、Swift2はとても素晴らしい indirect という修飾子を導入することにより、再帰的構造を持つ enum を定義できるようになりました。

また、挙動的に面白いのが、再帰的構造を持つが即座に評価されないようになっている場合、たとえば以下のような遅延ストリームについては、indirect 修飾子がなくても動くのです。これは Swift 1 系のときに挙動を試してないので、どうだったかわかりませんが、Xcode ダウンロードするのめちゃめちゃ時間掛かるし重いのでだれか試して欲しい。

にゃ〜ん…って感じですね?

関数内に定義した関数の再帰呼び出し

まあコードみればわかると思うけど、以下のようなコードが Swift 1 系では動かなかった(記憶がある…)。間違っていたらごめんなさい。でも関数内に定義した関数なんて、再帰呼び出しにくらいしか使わなくないですか?と半ギレした記憶があるので多分ほんとに動かなかったと思う。Swift2でやっと動くようになりました。

Swift 最高!!!!!!(大嘘)

Cocoaにおける同期

ひとつのアプリケーションに複数のスレッドがあると、同じリソースに対する複数スレッドから変更が意図せず干渉する場合がある。基本的かつ有効な方針は、共有リソースを減らし、スレッド間のやり取りを最小化することだ。

不変オブジェクトはスレッドセーフであるので、スレッド間で安全に受け渡しすることができる。また、そもそもあるオブジェクトが単一のスレッドからしか利用されない場合は、当たり前ではあるがなにも問題はない。Foudationの基本的なクラスはだいたいスレッドセーフになっているが、そうでないものもあるので注意が必要。

同期ツール

しかし、完全に干渉のない設計が常にできるわけではないのでその場合には同期ツールを使う。同期ツールには以下のようなものがある。

  • アトミック操作: 単純なデータ型を操作するだけの同期、スレッドをブロックしない点が特徴
  • メモリバリア: 確実に正しい順序でメモリ操作を実行させる
  • ロック: クリティカルセクションの保護ができる
  • 条件変数: 特定の条件に該当する場合にスレッド同士シグナルを送り合う

パフォーマンス

  • mutex: 0.2 μsec
  • compare and swap: 0.05 μsec

デッドロックおよびライブロックへの配慮

単一のスレッドで複数のロックを同時に取得しようとする場合は、常にデッドロックが発生する可能性がある。できるだけそういう処理を避ける。

アトミック操作

ハードウェア命令とメモリバリアにより、特定の操作を必ず完了してから、その操作の影響を受けるメモリへのアクセスが再開されるような仕組みになっている。

対応している演算は以下のとおり

  • Add: 加算
  • Increment: +1
  • Decrement: -1
  • 論理OR: OR
  • 論理AND: AND
  • 論理XOR: XOR
  • compare and swap:
    • 古い値と変数を比較して等しい場合に、新しい値を代入する
    • 比較と代入をアトミックに行う
  • test and set:
    • 変数ないのビットをテストし、このビットを1にして、元のビットの値をブール値として返す
  • test and clear:
    • 指定された変数内のビットをテストし、このビットを0にして、元のビットの値をブール値として返す

ロック

いろいろなロック

  • ミューテックス: リソースを囲む保護バリアとして機能する相互排他的なロック。一度に1つのスレッドだけにアクセスを許可するセマフォの一種。
  • 再帰ロック: ミューテックスロックの亜種。ロックを取得した単一のスレッドで、そのロックを解放する前に複数回ロックを取得できる。同じ回数アンロックをかけるとロックを解除できる。
  • 読み取り/書き込みブロック: 共有排他ロックのこと。規模の大きな操作で利用することがおおい。データの読み取りを頻繁に行いながら、部分修正するような場合にパフォーマンスが良い。POSIXスレッドを用いる。
  • 分散ロック: プロセスレベルの相互に排他的なアクセスを実装できる
  • スピンロック: 条件が真になるまでロックの条件を繰り返しポーリングする。粗相されるロック待機時間が短いマルチプロセッサシステムで用いられる。カーネルプログラミングにより実装する。

POSIXミューテックスロック

どのアプリケーションからも利用できる。

NSLock

tryLock() -> Bool とか lockBeforeDate など便利なやつもいる。

NSRecrsiveLock

同一スレッドによる複数回ロック取得が可能なロック。ロック数をカウントしているので、対応するアンロックがすべて走ってようやくロックが解除される。使い方は NSLock と同じ。再帰呼び出しが発生する場合はこれを使うべし。ただ再帰呼び出しが発生しないように実装できるならばその限りでない。

NSConditionLock

NSConditionLock は特定の値を使用してロックおよびロック解除できるミューテックスロックを定義する。

条件変数の利用

条件変数は、必要な順序に合わせて操作を進めるために使用出来るロック。ある条件で待機しているスレッドはその条件のシグナルが別スレッドから明示的に送られるまでブロックされたままになる。

NSConditionの利用

Appendix

参考文献

Apple公式のこれとかこれとかこれを読んだ。

用語の定義

ドキュメントの中では用語の定義が以下のようになされている。

  • スレッド: コードを実行する、他とは切り離されたパスのこと
  • プロセス: 動作中の実行形式コードのことで、複数のスレッドから成ることもある
  • タスク: 実行するべき処理を表す、抽象的な概念

スレッドの代替テクノロジ

  • オペレーションオブジェクト: NSOperation のインスタンス, 通常は OperationQueue に突っ込んで使う
  • Grand Central Dispatch(GCD)
  • 他、アイドル時間通知, 非同期関数, タイマー, プロセス

RxSwiftライブラリの作り方 〜Observer/Observable編〜

 RxSwiftライブラリの作り方をご紹介します。一つの記事ですべてを説明するのは非常に厳しいので、まず ObserverObservable といった基本的なコンポーネントとその周辺について、ひとつずつ作っていく流れで説明します。

注意事項

  • 以下の内容を理解しなくても RxSwift は十分使えるライブラリです
  • まだ Rx 系のライブラリを使ったことがない方は、まずライブラリを使ってみてください
    • Qiitaの記事を読むのもよいですが、公式のドキュメントExampleが充実しているのでそちらを読みながら、まずはコードを書いてみることを強くお勧めします。意外に簡単に使いどころが理解できるようになると思います。
  • 記事の内容的には Rx 系ライブラリの利用経験がなくても分かるように書いたつもりです
  • 以下の実装は RxSwift のものであり、他言語の Rx ライブラリとは実装が異なる場合があります

Observerパターンの復習

以下の問題について考えていきます

  • 【問題】 A が更新されたことを、B に通知したい
  • 【解決策】 A が B のインスタンスを保持し、B に 変更を伝える

解決策を単純に実装すると、次のような構造になると思います

 シンプルですが、問題をしっかりと解決できています。続いて「通知先が増えそうである」という条件が加わったを場合を考えてみましょう。このままでは、次のような問題が発生しそうです。

  • 通知先が増減するたびに A の内部を変更しなければならない
  • 通知先のI/Fの変更により A の内部を変更しなければならない

 この2点はともに、通知元が通知先のオブジェクトの詳細を知っていることによって生じている問題です。通知元はどうあがいても、通知先を保持しなければなりませんが、その詳細を知ったまま保持する必要はないはずです。

 したがって通知元は、必要のない情報をそぎ落とした状態で通知先のオブジェクトを保持すれば、問題が解決しそうです。つまり、通知先に共通のインターフェース(Swiftのプロトコル)を切ればよいということになります。

 また、通知元のオブジェクトの種類を増やしたいとすれば、こちらもインターフェースを切っておくと使いまわしが効いて便利です。ObserverObservable からの通知を受け取り始めるための attach というメソッドと、通知の受け取りを解除するための detach というメソッドがあれば十分でしょう。

 この形式を pull 型 Observer パターンと呼びます。Observer が通知を受けたあとに、Observable から値を引っ張ってこなければならないために pull という名前が付いています。以下のように Observable が更新時に Observer に対して値を投げるような実装も可能です。こちらは push 型とよばれています。

 この UML を Swift の実装へ単純に落とし込むことはできません。Swiftの protocol は、generic type parameter を持つことができないからです。代わりに関連型 (〜2.2: typealias, 2.3+: associatedtype) で表現する必要があります。

 ここまでくれば Observerパターンの基礎についてはなんとなく理解できるようになっているのではないかと思います。次節では RxSwift ではどのように push 型の Observer パターンに用いられる基本的なコンポーネントを構成しているかを見ていきます。

Observer, Observable を作る(Rxライブラリの下ごしらえ)

 ここからは実際に RxSwift でどのように Observer と Observable が定義されているかを見ていきましょう。基本的には push 型の Observer パターンをそのまま実装していけば良いだけです。ただしRxでは値を単純に通知するのではなく、成功(Next)、失敗(Error)、完了(Completed)という文脈をつけた イベント を通知します。また、ObserverObservable に登録した際に Disposable というI/Fを持ったオブジェクトを返し、そのオブジェクトに購読解除の機構を持たせている点も特徴的です。

したがって実現したい構造は下図のようなものになります。
 

ObserverType, ObservableType の実装

 前述したとおり、protocol には generic type parameter を用いることができないので、まず関連型を用いて ObserverTypeObservableType を以下のように定義します。

AnyObserver, Observable の実装

 さて、こうして定義した protocol を generic type parameter を用いたクラスに落とし込みます。Swiftの言語機能が不足しているので、ここは醜い表現になっていますが、 本来であればインスタンス化できないように abstract class にするような部分 だと思います。RxSwift 内では、苦し紛れですが @noreturn アノテーションを用いて抽象メソッドを表現しています。

 ここで登場するパターンは、なぜか皆さん大好きな type erasure ですが、これをやらなきゃいけないのは決して褒められたことではないと思います。個人的には普通にJavaより劣ってるでしょって感想です(←炎上しそう)。Swift の protocol が generic type parameter を持てない理由については、この記事によくまとまっているようですが、この点について自分は理解できてはいません。Swiftの言語仕様に阻まれ、随分遠回りにはなりましたが、無事 Observable<Element>Observer<Element> を定義することができました。

Observableの具象クラスを作ろう

Bag, SubscriptionDisposable の実装

 先ほど定義した Observable<Element> の実装クラスを作成してみましょう。実装が必要なメソッドは subscribe です。ここでは受け取った observer を保持する必要があります。
 
 observerO: ObserverType where O.E == E という制約のもと渡ってきますが、ObserverType のコレクションを作ることは残念ながらできません。なぜなら ObserverType は abstract type member を持っているからです。ここでは、型制約を利用して AnyObserver<E> のコレクションに突っ込んであげればよいでしょう。例が稚拙で申し訳ないのですが、Observable<String> であるような StringObservable を定義するとして、書き出しは以下のようになると思います(実際のライブラリにはこのようなクラスは存在しません)。

 subscribe に渡った observer は、observers に登録され、何かイベントがあったら通知されるようになりました。イベント購読の機能の実装はこれでとりあえずOKとしましょう(いろいろ細かい問題はありますがとりあえず置いておく)。
 
 続いてイベント購読解除の仕組みを作る必要があります。これは単に observers から購読を解除したい observer を削除してあげれば良いだけです。現状は配列に突っ込んでいますが、辞書的なものに突っ込んでキーを指定して削除できた方が、取り回しが良いでしょう。
 
 そこで、要素を追加すると同時に、 id を発行するコレクション Bag を作ります。差し当たっての問題は、どのように id を生成するのかということです。ここでは Swift の unsafeAddressOf メソッドが暗躍します。クラスのインスタンスはメモリを確保するため、インスタンスそのものがある種の id の役割を持っています。そして、メモリの番地を取得するのが unsafeAddressOf メソッドになります。

 こうしてコレクションのキーとなる BagKey の実装が終わりました。続いてコレクションの本体となる Bag について考えましょう。RxSwift の Bag の実装は要素数が少ないときに最適化されるような仕組みがのっていますが、本質だけ抜き出すとただの Key-Value store です。

 準備が整いました。もう一度やることを確認しておきましょう。Observable#subscribe では以下のことを行いたいのでした。

  1. 受け取った observerBag に突っ込む(通知先を保持する)
  2. 受け取った observerBag から一意探せるようなキーと、Observable 自身の弱参照を持ち、dispose で購読解除ができるような Disposable オブジェクトを返す

 2. のようなことができる Disposable の具象クラスとして SubscriptionDisposable というものを実装していきましょう。構造はとても単純で、以下のようなものになります。

 observer を所有している owner にキーを指定して購読解除ができなければならないので、 owner 自身は SynchronizedUnsubscribeType プロトコルに適合している必要があります。そういった部分も含めて StringObservable の実装を修正すると以下のようになります。

 これがマルチスレッド下で正しく動くかというと、またそれは別の話なのですが、とりあえず、シングルスレッド下で Observer パターンを実現させるための Rx の基本的なコンポーネントは出揃いました。利用側のコードを以下に示します。

 ここまできたらもう BehavoirSubjectVariable の実装を読むことができるようになっていると思います。マルチスレッド対応のためのロック処理なども完結にまとまっているので勉強になるコードです。次節ではその実装をみていきます。

BehaviorSubject, Variable の実装

BehaviorSubject

 前節では、String値を状態として持つ Observable の具象クラスを実装しましたが、これを一般化して任意の型の値を状態として持つような Observable があれば便利そうです。また、こうしたオブジェクトは通知元になりうると同時に、通知先になることもできそうです。この性質を SubjectType プロトコルにまとめます。普段は通知元として振る舞いますが、必要な時に通知先のインターフェースへの変換メソッドを呼び出せれば十分なので以下のように asObserver() を持っていれば大丈夫そうです。

 また、BehavoirSubject をスレッドセーフに実装するために NSRecursiveLock が使われています。これは lock している場合に同一スレッド以外からのアクセスを、unlock されるまで待たせることができるものです。使い方は、以下のように直感的なものです。

 スコープから抜けるときに必ず unlock が実行されるという点を強調するためなのか、RxSwift では defer を使った次のような書き方がちらほら見受けられます(とはいえ統一されているわけではない)。

 これらを踏まえた上で BehaviorSubject の実装をしていきましょう。基本的には前節で実装した StringObservable に対して、SubjectType, Disposable の実装と排他制御を追加しただけの構造になっています。

 クライアント側のコードは前節とほぼ同じ形となります。

Variable

 BehavoirSubjecton.Error.Completed を渡すことにより閉じることができてしまいますが、ただの値を Observable にしたいだけならそんな機能は要らないはずです。そこで、そういったインターフェースを隠蔽し、もっと変数ライクに扱えるようにしたのが Variable です。基本的に実装は、ただの変数ラッパー + asObservable のために保持しているBehavoirSubject ですが、不要なAPIの隠蔽と、変数として扱うのに便利な computed property を生やす役割を担っていると言ってよいと思います。

 こうして取り回しのきく Variable を作ることができました。RxSwift には他に PublishSubjectReplaySubject というような Subject (通知元にも通知先にもなりうるオブジェクト)が存在しますので、また機会があれば別の記事でご紹介したいと思います。また、ここまで記事の内容を理解しながら読み進めている方であれば、ソースコードを読みさえすれば何をしているか大体わかるのではないでしょうか。

まとめ

 歴史的経緯はともあれ、Rx の基本的なインターフェースである Observable, Observer については以下のように説明できると思います(自分は歴史的経緯は知らないので実際の流れは違うかもしれません)

  • push 型 Observer パターンが基本的な出発点
  • 値に next, error, completed という文脈をつけたものが push の対象物になっているのが特徴的
  • 購読解除の仕組みを Disposable に分離しているのが特徴的
  • 以上を踏まえると Observable, Observer といったインターフェースを自然に導き出すことができる

 また、Observable の実装クラスのうちのひとつである BehaviorSubjectVariable などについては次のようなことが言えます。

  • ある型のインスタンスを観測可能な状態(Observable)に簡単にリフトアップさせることのできる役割を持つ
  • 同時に、観測者側(Observer)にも変換できる状態にする役割を持つ
  • 中身は、単純に push 型 Observer パターンの Observable がやらなければならないことをスレッドセーフに実装しているだけ

 最後にもう一度書いておきますが、この記事の内容がわからなくても、RxSwift は使えますので、利用を迷っている方は、巷に飛び交う記事に惑わされず、是非公式の Example や Playground を真似して使ってみてください。

Appendix

ライセンス表記

RxSwiftはMITライセンスで公開されています。記事内のコードはライセンスに基づき、そのまま掲載している箇所や改変して掲載している箇所があります。

The MIT License Copyright © 2015 Krunoslav Zaher All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

class縛り

という感じで class で縛れる。structでは縛れない。