こんにちは @yimajo です。この記事は今から新規でAndroidアプリを書き始めるなら。に大きく影響されています。主な内容として次のような事柄を取り扱っています。
- 今から書くならこんな設計
- こんなライブラリがあるが使ってみた感想
ただ、結論として大して深い内容は書けませんでしたので、がっかりせず、みなさん思い思いにやればいいよっていうことに終着しています。アドベントカレンダーのネタにみなさんも書いてみてはどうでしょう。
言語について Objective-C か Swift か
まず最初に言っておくとObjective-CやSwift以外にもiOSアプリを始める方法はあります。例えばObjective-C++とかRubyMotionとか。まあそれはそれで良いところもあると思いますが、複数人でiOSアプリ開発を行いそれを保守したり機能追加したりすることを考えるとObjective-CかSwiftかの2択になるでしょう。
その2択なら、どちらを選んでもいいと思います。ただ新規に始めるならSwiftからでしょうね。Swift3から始められるような新規プロジェクトならObjective-Cをメインとすることはないと思います。
しかし、ときにはObjective-Cを使ったほうが早く実装できる場合もあります。例えばAudioToolbox.frameworkのように昔からある古いフレームワークはSwiftでそのまま利用できるようにしているものが多く、APIのローレベル感が強いのでリファレンスを見ても何をしているか判断がしにくいため、サンプルコード(というか人がどう使っているか)を見てやっと使えるという感じです。
具体的には次のメソッドなどがそうです。
func AudioServicesSetProperty(_ inPropertyID: AudioServicesPropertyID,
_ inSpecifierSize: UInt32,
_ inSpecifier: UnsafeRawPointer?,
_ inPropertyDataSize: UInt32,
_ inPropertyData: UnsafeRawPointer) -> OSStatus
AudioServicesSetProperty
https://developer.apple.com/reference/audiotoolbox/1405226-audioservicessetproperty
上記はシステムサウンドのプロパティをセットするものです。そもそもなんでこんな物があるかというと、アプリの効果音の設定を変更したい場合に使ったりします。カスタマイズしたいというメソッドなのに引数にSizeが2つ、そしてUnsafeRawPointerを2つ渡します。これはわかりづらい。カスタマイズするぞってときにUnsafeRawPointer。何かのポインターを渡してそのサイズを渡すというのはかなりローレベル感が強い。このようなメソッドのサンプルコードは今のところはObjective-Cのほうが多いよという話です。システムサウンドをカスタマイズしたいときのような、とりあえず望んでいる効果を最低限満たせればいいという場合に無理してSwiftでどうするかを考えなくてもいいよと。
実際はメソッドの引数はObjective-Cと同じなので、Objective-Cの数あるサンプルコードを見てSwiftで書き直したければそれでいいとは思います。自分たちに与えられたリソースに応じてバランスよく判断したらいいですよねって思います。
Swiftのアップデートの件
(この項目は新規でiOSアプリを開発することとは大きくずれていますが、書いたあと気付いたので残しておきます)
Swift3へのアップデートが互換性を気にしないものだったことで、Swift2系から利用しているかなりのアプリ開発者は苦労をしたりこれからすることになっているとは思います。その理由を箇条書きにしてみると
- 複数人で開発している場合は日常の機能追加・不具合修正の作業と並行して行うため、時間がかかればかかるほど作業量が増える
- Swift3対応自体が複数人で分担してやりづらいので一人でやることになり作業スピードが早くならない
- 利用しているライブラリがSwift3対応していなければいけない
- 利用しているライブラリがSwift3対応してもターゲットバージョンもアプリと同じかそれ以下をサポートしていなければいけない
- 利用しているライブラリがSwift3対応のついでにインターフェースを変えてきたらそれに応じアプリ側も変更しなければいけない
- 利用しているライブラリが期待通りの動作をするか動作確認をしなければいけない
- 戻り値がnilじゃなかったAPIがnilを返すようになったりするとすでにある実装にエラー処理を変更するか悩む
- コンパイルエラーが一度にすべて出ず途中で終わるのでエラー数を減らしても達成感が少ない
- General->Issues:Continue building after errorsにチェックすると良さそうなのにそれでも止まる
特に複数人でのくだりが辛い。大企業の巨大アプリは特につらくて10人を超える開発者がいる場合はこちらが想像できない辛さがあると思います。そういうのはコメント欄に書いていただけるといいんじゃないでしょうか。
設計について(MVCやMVVMなどのパターンについて)
設計についてという項目ですが、もっと細かいMVCなどクラスの役割分担についての話題です。
iOSアプリのクラスの役割分担についてはAppleのドキュメントを見るとAppleのMVCとデリゲーションについて以下のように書かれています。
iOSのフレームワークは「モデル-ビュー-コントローラ」などの設計パターンにもとづき、委譲(デリゲーション)の仕組みを使って実装されています。この設計パターンの理解が、アプリケーションの成功には不可欠です。さらに、Objective-C言語やその機能を知っておくことも大切です。iOSプログラミングが初めてであれば、iOSアプリケーションやObjective-Cについて紹介した、『Start Developing iOS Apps (Swift)』を読んでおいてください。
ただ、複雑なアプリケーションを複数人のメンバーで組み立てていく際に困るのはモデルというものが細かく定義されていないことで、本質的な意味での設計がメンバーごとに変わってしまい秩序がなくなってしまうということです。フレームワークを作る際はMVCで十分なのでしょうが、現実的なiOSアプリには、さまざまな種類の処理があり、それらを雑に分けると、「そのアプリ固有の要件に基づいた処理」なのか、「ほかアプリでも使えるような処理」なのか「提供されたAPIのラッパー」などで大きく違いがある。
ここでMVCしかないという縛りの中で、コードレビューをする例を挙げてみます
- A「このViewの処理ってViewに書かなきゃいけないんですかね?Modelを作ってそこに移しませんか?」
- B「なるほどViewに書くような処理ではないという指摘はもっともかもしれません。Modelを作ります」
- (一時間後)
- B「HogeManagerを作りました。コードレビューをお願いします」
- A「なるほど。(Managerが出てくるのか...)」
何が悪いという話ではないですが、MVCでのModelというのは自由すぎるため、どのような場合にどのようなModelを使うかというのがしっかり決まった上でメンバーが納得感があればいいですが、その自由な設計自体難しいですからね。オレオレModel設計になったときの無秩序感がある。
その解決策の一つとしてMVVMやMVPを採用することはコンセンサスが取りやすい。とくにMVVMはModelの情報をViewModelを利用してViewにバインドすることが必須のため制約があります。オレオレMVVMにはなりにくい。
そういう意味では、レイヤードアーキテクチャも解決策になるんじゃないかと思います。レイヤーを意識することでどこまで処理を抽象化したり奥深くでやればいいのか考えざるを得なくなることで、アプリの設計を変更する際に、変更による処理の依存度がどこまであるかの見当がつきやすくなるというメリットもあります。これはオレオレ設計になりやすいとは思いますが秩序だっていて後で直しやすいでしょう。
ライブラリについて
ライブラリを使わなくて済むならそれが一番じゃないですかね?
Swift定番ライブラリとやらの記事を目にすることがありますが、それたいてい自力でできるのでそういうのを見て何も考えずに採用するのはオススメできない。逆にやろうとするとどうしても時間がかかるOpenCVとか機械学習のライブラリとかは数が少ないのでそれらの定番ライブラリ紹介に意味はありますね。
あと仕事のプロジェクトでライブラリを利用したPull Requestを出されると、コードレビュー時にライブラリのコードを読むようにしたり動かしたり私はします。そうすると結果開発時間は全体的に短縮されてないような気がして悩ましいです。
と、ライブラリについての立場表明もできたところで、用途に応じたライブラリについて書いてみます。
通信編
Alamofire
いわゆる定番通信ライブラリとして紹介されるライブラリですが、これ使わなきゃいけないような場面に遭遇することはほとんどないです。このライブラリの使い方をブログで解説されてることが多いので、通信の仕方を調べやすいという意見があるのを目にしたことがありますが、そのためにこの巨大なライブラリを使わなきゃいけないのはバランスが悪すぎる。機能はたくさんありますが、それが必要になってから方法を検討したらいいだけで必須ではないです。定番を知る前に基礎を抑えることのほうが遥かに楽で重要です。
開発者はmatttさん、ではなくとっくに違う人になっているのもポイントで、Swift1から2になったときもインターフェースの変更を合わせてmasterブランチにマージしてるのにplaygroundなどのサンプルコードを変更してなかった時点で、私は人にはオススメできないなと思っています。
JSON編
SwiftyJSON
これもSwift初期からの定番JSONパーサと言われてるライブラリ。いやJSONをパースしてるのはFoundationのJSONSerialization(旧名NSJSONSerialization)ちゃうんかというわけで実際はJSONの結果をもとの型に応じて取り出せたりと簡単に処理するライブラリ。JSONSerializationを扱うのとそんなに大差はないでしょう。
むしろこういう定番ライブラリがなくてもiOSアプリは十分に作れるようになっていて、自分でアプリを開発していると特定のパターンが見えてくる。そういうパターンを定型化してくれる小さなライブラリは好きに使えばいいと思います。ライブラリの仕様変更や動作によって振り回されたり、定番という言葉やGitHubスター数だけで判断してライブラリを導入するのは結局あとで時間を消費させられる原因になります。
FRP編
RxSwift
RxSwiftのような大きくてパラダイムを大きく変えるライブラリはメリット・デメリットをチームで考えて採用するかどうか決めるのが一番じゃないかと思います。メリットデメリットを考えずにやるというのは具体的には次のような事柄です。
- RxSwiftが流行ってるらしいので自社アプリの新機能をRxSwiftで作る
- 自分以外FRPとか知らんしMVVMも知らん
- 顧客に頼まれたアプリ作り方は自由だとのことでRxSwiftでコードを書いて納品する
- それを保守運用する人が誰かは知らん
顧客(実際に使うユーザやお客さん)のためではなく自分の成長のためにやるんだったら副作用が強すぎるんですよね。特定の技術範囲の知見があることを成長と呼ぶかどうかは知りませんが。
逆にRxSwiftを使って幸せになれる人のパターンを考えてみました。
- 複雑なイベント処理/非同期処理などは他の方法で幾らでもできることを認識している人たち
- Rxのオペレータに関してオペレータの変更でアプリの仕様変更に簡単に追従できるイメージが湧く人たち
- MVVMを採用するメリットが大きいと感じる人たち
RxSwift使って良かった人の例はもっとあるかもしれないのでSwift3対応の辛みと一緒にコメントに書いてもらえればいいかもしれません。
もう少し書くと、個人的にはFRPのような流れを意識してコードを書くというのは自分が欲しかったものだという気はします。ただRxのオペレータ名(たとえばzip, amb)がどうしても私の直感に合わないので学習コストがどうしても高くなる。Rxは他のフレームワークにもあることが良さで、オペレータ名が同じこともメリットではありますが、オペレータ名自身が私に分かりづらいのはデメリットに感じます。そこが改善された他のフレームワークとまたがるライブラリが出ればまた話が違うなというのが感想です。
リソース編
R.swift
R.swiftはAndroidのRのようにリソースを扱えるライブラリ。ビルド時にリソースの状況からstructのコードを生成し、文字列でアクセスするリソースをメソッドとして提供します。
それだけ聞くとデメリットとしてビルド時間が延びるような気がしますが体感で遅いと感じたことはありません。ただR.swiftを使ってないプロジェクトを見るとリソースに文字列でアクセスしていることによる不安を感じる。つまり、知らない間にR.swiftは安心さを提供しているわけで、リソースが消されたりするとコンパイルエラーになって気づかせてくれるというメリットもあります。
リソースというのはStoryboardの記述も含んでいてsegueに関してもメソッドでアクセスできる、ただしコードが短くなるわけではないのでその作法は覚えづらいので、これですごく楽になるというわけではないです。なければなくてもいい。
データベース編
選択肢は3つしかないという気がします。
- SQLite+FMDB
- Core Data
- Realm
まずSQLite+FMDBは無い。もうこの組み合わせを選ぶメリットないでしょ。もしかしたらSQLiteで全文検索するためにFTSを利用したいというパターンがあるかもしれないけど今のiPhone端末が速度的にFTSを利用しないと検索が遅いという要件になるとは思えないので、FTSは高速と決めつけず計測して決めるほうが方がいい。
Core DataはNSFetchedResultsControllerがこちらが想像してないほど強力、保存してあるデータを日付ごとにsectionに分けたいなどの場合はこういう方法がある。絶対これ知らなきゃ思いつかない。当然、フェッチしたデータが変わった場合それをdelegateでコールバックする仕組みもある。
MagicalRecord
Core Dataを利用する場合の薄いラッパーとしてactive recordのパターンに似せたMagicalRecordがあります。これは薄いラッパーなのでこのライブラリをプロジェクトに入れていても部分的にMagicalRecordを利用し直接Core Dataを扱うことは頻繁にあるので、慣れたらMagicalRecordを使わなくなって外してもよい感じのライブラリです。
iOSのフレームワークを使いやすくする系
ProcedureKit
NSOperationsをラップして使いやすくするライブラリ。これは非同期処理のロックなどをわかりやすくする系です。ただし、AppleのMapKitやEventKitなどもimportして使いやすくしてくれるようにしている。おかげで申請時にEventKitにアクセスしていることでユーザのカレンダーにアクセスするような許可用の文面をplistファイルに書かなければいけない。これが申請してまたビルドし直す必要が出てきてきっつーな状況になる。コードがでかいしNSOperationsの使い方覚えればいいだけではないかと思う。
Cocoapods を使うか Cathage を使うか
チームで自分たちにあった方法を選択しろ。
という感じですが、私の経験上Cocoapodsにはかなり消耗させられています。これはRubyで書かれていて複数のgemsに依存していることも原因の一つじゃないかと感じていて、その結果としてチームメンバーのgemsを合わせたくなる。つまり.Gemfileを置いてBundlerでpod installしたくなる。新しい環境でpod installできないがgemsをupdateしてgem xcodeprojのバージョンを上げるとinstallがうまくいくようになったり、原因は追求してないがとにかくgemsを新しくしたら良しみたいなこともあったりもします。
Cathageは初回のビルドが遅いのはありますが、ビルド済みのファイルさえチームメンバーに共有してもらってCarthage/Build/iOS以下にコピーしておけばそこは開発時のネックにはならないし、そうでなくても開発マシンのスペックを上げていけばいいのであまりネックにはならないかもしれないです。チームで開発するならむしろCathageを使わない理由がないかもしれないですね。
俺はこう思ったっす
最近、Xcode8.2がSwift2.3をサポートする最後のXcodeと発表されましたが、まだまだObjective-Cは使えているわけで、すぐにはObjective-Cが無くならないんじゃないのという気持ちです。そもそもApple自身にObjective-Cのエキスパートが世界一いて、彼らが毎年決まって大きくアップデートするiOSやmacOSの開発にObjective-Cを使わないとは到底思えません。
Objective-Cだとビルドできないという未来が近い将来あるとしたら、それはApple自身も痛みを伴うことになるんじゃないかと。そうでなく、Objective-Cのコードが有ると審査でリジェクトされる、という想像もあるかもしれませんがそんなことしてAppleに残ってる大量のObjective-C製のコードを保守できるエキスパートを外から調達できなくなるので、デメリットデカすぎでしょと。十数年後かに今のiOS/macOSと全然違ったものが作られたなら話は別ですが。なので数年単位のレベルだとObjective-Cでコードを書くことも悪いとは思わないですね。
また、iOSアプリ開発の特徴はいろんな開発者がいるところだと思います。その中でもクリエイター気質な人達はSwift使わず今もObjective-Cで書いてSwift3対応の辛みなんて全然気にせず前へ進めるということも良さの一つでしょうね。