騒音のない世界 BLOG

コンピュータの話と、音楽の話

UIPasteboardをもっと理解する (前編)

UIPasteboardといえばstringプロパティやimageプロパティでコピーペーストできるやつ、ぐらいに思ってませんか。この記事ではその概念と注意点をしっかり押さえつつ、具体的な動作例を見ていくことでUIPasteboardの理解を深めていきます。

(ちょっと長すぎたので概念と具体例で前後編に分けます。今回は前編になります。)

UIPasteboardとは

公式docの冒頭を要約すると、UIPasteboardは「データを自分のアプリ内のある場所から他のあらゆるアプリに共有するもの」のようです。典型的な使い方はデータコピーなどの際にUIPasteboardを通してデータを保存しておいて、アプリ内や別なアプリから再びUIPasteboardを通してデータを読み出して使う、といった感じです。単純なテキストや画像のコピーだけではなく、バイナリや独自データを他のアプリに転送するような用途でも使用できます。

UIPasteboardの種類

大きく分けて2種類のペーストボードがあります。

The General Pasteboard

どのアプリからも自由にアクセスできる、システムワイドなペーストボードです。UIPasteboard.generalでシングルトンとしてアクセスできます。メモ帳アプリなどで普通にコピーするとこのペーストボードに保存されます。逆に言うと、General Pasteboardをプログラムから操作することによって現在コピーしているものが書き換わってしまい、ユーザーの混乱を招く恐れもあるので注意が必要です。

iOS10からはHandoffの新機能として、複数端末間でGeneral Pasteboardに保存したデータを共有できるようになりました。こちらは共有不可にする設定も可能です。

Named Pasteboard

アプリ内で完結するようなものや、Team ID が共通なアプリ間でのデータの受け渡しにはNamed Pasteboardが利用できます。これは、一意な名前をつけて生成され、システムを汚さず独占的に使用できるペーストボードです。

何を保存するのか

UIPasteboardは何を保存するのでしょうか。UIPasteboardのヘッダを見るとこういう部分があります。

// Direct access

open var items: [[String : Any]]

Direct accessという言葉が示すように、このitemsプロパティでアクセスできるitemが、ひとつのペーストボードに保存されるデータそのものです。これはPasteboard Itemと呼ばれるもので、ひとつのPasteboard Item[String : Any]というDictionaryとなっています。var宣言されているため追加、削除、挿入など何でもできます。そして、これ以外のメソッドやプロパティのほとんどはこのデータに便利にアクセスするものです。

つまり、ひとつのペーストボードが保存しているのは「何でも保存できる辞書」の配列ということです。これが意味するのは、ペーストボードが保持する値は1つではないということです。普段単純にコピーペーストなどしていると新しいものをコピーすると古いものは消えるのが当然のような感じがしますが、ペーストボード自体はプログラム的には(リソースが許す限り)いくつでも値を保持できます。

Pasteboard Type

Pasteboard ItemのDictionaryにおけるキーとなる値がPasteboard Typeです。この実体は単なるStringであり、意図せず重複してしまうことがなければどんな文字列でも指定することができますが、UTI (Uniform Type Identifier)の使用が推奨されています。

UTI (Uniform Type Identifier)

UTIはAppleが制定しているデータフォーマットを識別する識別子で、reverse-DNSで書きます。UTIについてはこちらのドキュメントが詳しいです。 Introduction to Uniform Type Identifiers Overview

例えば、com.apple.quicktime-moviecom.apple.pict public.jpegのように書きます。

また、UTIは継承でき、is-a関係を持ちます。public.dataを継承してpublic.textpublic.imageという型が定義され、public.imageを継承してpublic.jpegpublic.pngが定義される、といった具合です。

同じような役割を果たすものとしては、古くはOSTypeという"JPEG"のように4文字でファイルタイプを識別するものがあったり、他にはファイル拡張子やMIME typeなどもそうですが、UTIはより柔軟で拡張性が高いとのことです。

ちなみに、特定のアプリ内でしか使わないデータフォーマットの場合は<app-bundle-id>.foo.barのように命名するようです。

まとめ

  • ペーストボードにはGeneralとNamedの2種類ある
  • 1つのペーストボードは「何でも保存できる辞書」の配列を持つ
    • 1つの辞書をPasteboard Itemという
    • itemsプロパティですべてのデータにアクセスできる
    • stringプロパティやimageプロパティなどは単にitemsの中のデータに便利にアクセスするためのもの
  • 辞書のキーはPasteboard Type
    • 標準化されたデータフォーマット識別子であるUTI形式で指定する

いかがでしたでしょうか。前編はここまでです。後編では具体的な動作例や注意点について見ていきます。

UIPasteboardをもっと理解する (後編) - 騒音のない世界 BLOG

SoundCloudの再生数といいね数を振り返る('10~'16)

2010年8月より、SoundCloudで少しずつ自作の楽曲をアップロードしてきました。 騒音のない世界のサイトYoutubeチャンネルもありますが、楽曲は全てSoundCloudに上げています。

beco | Free Listening on SoundCloud

2017年1月12日現在、51曲アップロードされており、総再生数はおかげさまで35万回を超えています。 いつも楽曲を聴いてくださっている皆様、コメントや楽曲のご利用報告をくださる皆様ありがとうございます。

今回はこれまでを振り返る意味で、再生数の推移といいね数の推移について書きたいと思います。

再生数の推移

f:id:a_beco:20170112010008p:plain

2016年の総再生回数は129,270回でした。 2010年はほとんど見えませんが61回でした。

いいね数の推移

f:id:a_beco:20170112010435p:plain

2016年の総いいね数は506でした。 2010年は0回, 2011年は2回、2012年は8回です。 いいね数は再生数と比べて2016年の伸びが顕著ですね。

楽曲ごとのいいね数

f:id:a_beco:20170112011227p:plain

楽曲ごとのいいね数です。 公開時期などもあるので必ずしも人気順ではありませんが、1位は「新生活」でした。

2位は初音ミク楽曲の「強欲な娘」です。

インスト曲とミク曲が両方並んでいてなんかうれしいです。

おわりに

最初はアップロードしても全然聴かれていませんでしたが、徐々にコメントもらえたり、いろいろなところで使ってもらえるようになってきてありがたい限りです。音楽的にやりたいこともだんだん具体化してきたかなーという感じがしています。

これからももっと良い曲を届けていきますので、応援していただけるととても嬉しいです。 気になった方はサイトのお気に入り登録、フォロー・チャンネル登録、よろしくお願いします。ぐっときたらいいね・リツイートなどもしていただけるとありがたいです!

動画制作されてる方はBGMとしてのご利用も自由ですのでぜひどうぞ。商用利用も可です。詳しくはサイトを御覧ください。

では、2017年もよろしくお願いします〜。

Onion Architecture と Clean Architecture

以前にiOSにおける Clean Architecture 構成について、いくつか記事を書きました。Clean Architectureって何?という方はまずそちらの記事をご覧ください。

引き続き色々と調べておりまして、Clean Architectureの元になったアーキテクチャの1つであるOnion Architectureについて記事を読んでいて少し発見があったので書きます。

The Onion Architecture

背景

記事を書いてるのはJeffrey Palermoという方だそうです。Clear Measureというソフトウェア開発会社のCEOの方みたいですね。すごい経歴。。

記事は2008年ですが、その時点で何度か対外的に発表されているようなので大体10年前ぐらいのアイディアってことでしょうか。iPhoneの初出が2007年なので、スマホが現れ始めた頃ですね。

4つのルール

f:id:a_beco:20161231220524p:plain

  • アプリケーションは独立したオブジェクトモデルを中心にして構築される
  • 内側のレイヤがインタフェースを定義し、外側のレイヤが実装する
  • 依存の方向は内側に向かう
  • アプリケーションの核となるコードはインフラ(DBなど)とは分離してコンパイル&実行できる

どういうものに向いているか

  • 小さなウェブサイトには向いていない
  • 長く運用するアプリに向いている

どういうものなのか

  • 依存ルールがインタフェースの使用を強制し、疎結合化する
    • テスタブルにする
  • インフラを円の外部に起き、アプリケーションコードから依存しないようにする
    • インフラを置換しやすくする

伝統的なLayered Architectureの欠点

  • UI -> Business Logic -> Data という依存性
    • 各層が下層に依存し、各層が様々なインフラに依存する
  • 依存自体は必要なものだが、不要な依存を産んでいるのが問題
  • Business LogicがなければUIは機能せず、DataがなければBusiness Logicは機能しない
  • 特にデータアクセス技術は頻繁に変更される
    • 歴史的には3年ごとにデータアクセスのテクニックは変更される
    • 3年おきに入れ替える必要が出てくる
    • 密に結合していると、現実的に入れ替えが不可能になる危険がある

Clean Architectureとの関係性

そもそもClean ArchitectureはOnion Architectureを含むいくつかのアーキテクチャを自然な形で1つに統合するもの、という風に書かれています。なのでコアなコンセプトとしてはかなり似ています。目立って違うなあと感じたのは、

  • Onion Architectureは、依存が内側に向かっていれば層を飛び越えても良い
  • Onion Architectureには明示的なアダプタ層(Interface Adapter)が無い

といった点でしょうか。

依存を内側に向ける意味

前回、「依存を内側に向けることの意味がわからん」という話を書いたんですが、「インフラ技術は3年程度で新しくなる」という具体的な話があり、イメージが湧きました。3年以上運用することが明らかな場合、絶対に訪れるリスクとして備えておく意味があるなあと。Clear Measureでも古い技術のアップグレードとかやってるみたいなので、様々な現場のつらみから必要に迫られて生まれたアーキテクチャなのかなあと。そういう意味でも、やっぱりどちらかというと業務ソフトウェアを前提にしてるのかなあという印象が強まりました。

おわりに

アーキテクチャの話ってマクロで抽象的なのでコアなコンセプトとその意義を理解するのに時間がかかりますね。。引き続きアンテナ張っていきたいです。また何かあれば書きます。

サービス開発に Clean Architecture を採用するのは妥当か?

以前 iOS Clean Architecture という記事を書きました。Clean Architectureとは何か?とかClean Architectureの実装例などが見たい方はまずそちらをご覧ください。今回は開発の中で感じたモヤモヤを書きます。はっきりした主張がなくて論旨がわかりにくいのですが、ご承知の上で読み進めていただければ幸いです。

Clean Architecture が前提にしている「安定性」

前回も述べたように、Clean Architectureの重要なコンセプトとして「依存ルール」があります。依存関係は円の内側に向かうようにする必要があります。なぜならば、アプリケーションフレームワークやDBやWebサーバなどの外部の変更に対してアプリケーションのビジネスロジックが影響を受けないようにしたいからです。言い換えれば、「コードが変更された時の影響範囲を出来る限り狭めたい」という意図があります。これはいわゆる安定依存の法則であり、特にClean Architectureが起源のアイディアではありません。安定依存の法則に従うことによりソフトウェアの動作を安定させる効果もありますし、副次的に差分ビルドにかかる時間を短くし、素早いイテレーションを実現するといった効果もあるかと思います。

この依存ルールの裏には、「ビジネスロジックは比較的安定である」という前提があります。つまり、画面の見た目が変わったり、DBなどの保存先が変わることはあっても、ビジネスのルールが変わることは比較的少ないよね、という前提です。たぶん経験則によるものです。

ビジネスロジックは常に安定か?

ビジネスロジックが安定」という前提は、「ソフトウェアが既存の業務を置き換える」という状況においては(業種にもよりますが)そこそこ正しいのではないかという感じがします。スケジューラやTODOのような汎用的なツールについてもそうなんじゃないでしょうか。

しかし、近年のWebアプリやスマホアプリなどのサービス開発においてその前提はどこまで成り立つのでしょうか?SNS系のサービスは現実の作業を置き換えるものではなく、新たな体験と提供しているといえます。このような類のサービスにおけるビジネスロジックと、業務アプリケーションなど現実の作業を効率化する類のソフトウェアのビジネスロジックはどちらも同じく安定と言えるんでしょうか。

根拠に乏しい私感になりますが、このへんを全く同列に扱うのは少し乱暴だなあと何となく感じています。業務アプリケーションでないソフトウェアに対してビジネスロジックという言葉を使うのにも少し違和感を感じます。

もちろん共通的な部分もたくさんあるし、アプリケーションレイヤは全部不安定だろってわけではなくて「このへんは安定してるな」と思ったりもするんですが、Domainというよりは各層のコアな機能とか、Utility的なところだったりすることが多いなーと思うんですよね。例えば、HttpClientってデータ取得だからDomainというよりData的な外部の層におけるコア機能って感じで考えられるのかなって思うんですが、そんな頻繁に変えませんよね。まあそういう部分は基本的に「使われるもの」なので、安定依存の法則に違反するものではないんですが、Domainじゃないけど安定してるやんっていうか。

よくある変更に対する変更箇所が多すぎる問題

新規でイチから開発してるからかもしれませんが、決して特殊ではない変更に対して変更箇所が非常に多くなってしまうことがあり、安定依存って言ったじゃんヤダヤダとなることが理想と現実のギャップとしてあります。

例えば「ビューに追加で情報を表示したいからサーバから受け取るデータを変更する」という状況はクライアントの開発でよくある場面かと思いますが、この場合にCleanArchitectureを採用しているとどうなるかというと、Dataの変更をPresentationに伝えるため必然的にDomain(UseCase)も変更することになります。それぞれの層で使っているデータ構造をすべて書き換え、変換処理も書き換える必要があります。作業時間がボトルネックになるというほどではないですが、変更が機械的で退屈なことが多いです。

このあたりに関しては感覚的にはワーストケースに合わせているという感じがしています。なのでどうしてもシンプルな部分における変換層の単純作業感や記述の重複感があり、どうなのかなあと思うところです。

結局どこに依存すれば良いのか?

安定依存というコンセプト自体は間違っていないと思うのですが、結局安定な場所ってどこなんでしょうか?

Clean Architectureというより安定依存の話なのですが、これって要は「たくさん変更される場所」や「ほとんど変更されない場所」をあらかじめ予測せよってことなのかなって思ってます。で、アーキテクチャでそれをルール化してチーム内で強制するようなアプローチってなると、やっぱりClean Architectureみたいな、統一的にこのへんに依存せよ的な構成になっちゃうのかなあと思いつつ、現実的には安定的な部分ってアプリごとにもっとバラけてるんじゃないかな、と思ったりもしています。

だから、そこそこうまくいくけど最適なアプローチってわけでもないのかなって思うんですよね。

おわりに

お前は何が言いたいのかって感じですが、Clean Architectureの利点としてよく言われる「ファットコントローラを防げる」とか「テストが容易になる」とかって単にモデルをきちんと分離して疎結合な構成でルール化すればLayered Architectureで良くて、Clean Architectureならではな部分って「円の中心に向かって依存する」っていう依存ルールとか、Interface Adapterの存在とかかなて思うんです。で、安定してるところに依存するのが良いというのはわかるんですが、その円の中心って本当にそんないつも安定してるか?もっといい切り口があるのでは?みたいな話でした。

まあ多様な集合に対して統一的なルールを決めるのって大抵例外的なものがあるから100%うまくいくってことはなくて、大枠の方向性として70%ぐらいでそこそこうまくいけばいいかみたいな感じになりがちですよね。もしくは、結局ビジネスロジックが安定してるっていうのもあくまでも「比較的」っていうだけで期待するほどは安定していないものなのかもしれないですね。そういうのって何かしらの実験で数値化されてたりするんですかね?

あと、Onion ArchitectureとかはClean Architectureの元になってるっぽくて、情報が少し多いのでこの辺も合わせて読むとより理解が深まるかもしれないなあとちょっと思ってます。パッと見で違いがわからないぐらい似ているのですが。そのうち読みたいです。

→ 2016.12.31 追記: Onion Architectureについて書きました

ダラダラ書いてすいません。最後まで読んでいただいてありがとうございます。僕が感じてるモヤモヤを汲み取って「この本読むといいよ」とか教えてもらえたらとてもうれしいです。

CocoaPodsのinstallとupdateを正しく使い分ける

TL;DR:

  • 新規でpodをインストールする -> pod install
  • podを削除する -> pod install
  • podのバージョンを上げる -> pod update [PODNAME]
  • 利用可能な新しいバージョンがあるpodをリストアップする -> pod outdated

CocoaPods

CocoaPodsはiOSで幅広く使われているライブラリ管理ツールです。ライブラリの検索、再帰的な依存性の解決、コードの取得・管理までやってくれる便利なツールで、導入されている方は多いかと思います。

CocoaPods.org

install と update の間違い

CocoaPodsにはpod installというコマンドとpod updateというコマンドがあります。「CocoaPods」で検索すると上位に入門記事が色々出てくるのですが、これらのコマンドの使い分けについては間違えているものが多いです。多くの場合書いてあるのは、

  • プロジェクトに最初に導入する時だけ pod install
  • それ以降は新規インストールでも pod update

なのですが、これが間違いです。

本当の使い方

実は、この間違いが結構流布されているようで、公式サイトで言及されていました。

CocoaPods Guides - pod install vs. pod update

日本語で紹介している記事はなさそうだったので、冒頭だけ翻訳したいと思います。

Many people starting with CocoaPods seem to think pod install is only used the first time you setup a project using CocoaPods and pod update is used afterwards. But that's not the case at all.

「CocoaPodsを使い始めた多くの人はpod installをプロジェクトのセットアップ時に1度だけ使い、その後はpod updateを使うと思っているようだが、まったく違う。」

  • Use pod install to install new pods in your project. Even if you already have a Podfile and ran pod install before; so even if you are just adding/removing pods to a project already using CocoaPods.
  • Use pod update [PODNAME] only when you want to update pods to a newer version.
  • 「新しいpodをインストールするときにはpod installを使うこと。すでにPodfileが作られており、以前にpod installしていたとしても、すでにCocoaPodsを導入しているプロジェクトのpodを追加/削除するときであってもそうだ。」

  • pod update [PODNAME]はpodを新しいバージョンにアップデートしたいときだけ使うこと。」

はい、そういうことです。

記事中ではさらにコマンドのディテールも紹介されていますので一読されると良いかと思います。ざっくりいうと以下です。

  • pod installPodfile.lockを見て、まだインストールされていないものだけ依存性を解決し、すでにインストールされているものに関しては利用可能な新しいバージョンがあったとしても何もしない

  • pod update [PODNAME]Podfile.lockを無視してPodfileで指定されている利用可能なバージョンの最新を取ってくる

    • PODNAMEを省略するとすべてのpodをまとめて更新する。

おわりに

pod updateでも新規インストールできるので、誤情報を信じて新規インストールのたびにpod updateしてる方も多いのではないでしょうか。私がそうでした。新規に入れたいだけなのに関係ないpodまで更新されるし、新規に入れるものはPODNAMEに名前を指定できないので困っていたのですが、公式にわかりやすく丁寧な記事があって助かりました。

日本語のブログ記事は結構古いものも多いので、CocoaPodsの公式サイトのGUIDEを読むと正確な情報を得られるし、高機能なツールなので知らなかった使い方などわかってヨイですね。

最後に蛇足ですが、pod outdatedを使うと新しいバージョンがあるかどうかを調べられるので便利です。定期的にライブラリに新しいバージョンが出ていないかチェックするのに使うと良いかと思います。

iOS Clean Architecture

iOS その2 Advent Calendar 2016 15日目の記事です。

現在業務で製作中のアプリではClean Architectureを採用しています。アプリ自体はまだリリースされていないのですが、少しずつ全体像は見えてきたのでClean Architectureの概要と合わせて具体的な実装例の紹介と現時点での所感などを書きたいと思います。

Clean Architecture とは

f:id:a_beco:20161211205919j:plain 引用: The Clean Architecture | 8th Light

Clean Architectureは、Uncle Bob(ボブおじさん)によって2012年にThe Clean Architectureで提案されたアーキテクチャの一種です。上図はその概念を集約したものです。@tai2さんによる日本語訳もあります。

概要

こちらでは上図を元に要点のみを解説します。まず、アプリケーションのドメインとなるビジネスロジックが「中核」を担います。これは円の内側に配置されているUse CasesとEntitiesを指します。反対に、ビューやDB、Webサーバなどは「外界」です。これは図中では青い円(External Interfaces)を指します。さらに、この間に位置する緑の円がInterface Adapter、「変換層」となります。この層はアプリの「中核」と「外界」の間に立ってそれぞれの世界で都合の良いようにデータの形を変換する役割を担います。

最も重要なルールは、依存関係は常に内側に向かうようにするということです。内側の円は、外側の円については何も知りません。クラス、関数、変数、すべてのものについて知ることはありません。これは、一般的にドメインのほうが高レベルで抽象化されており、安定しているからです。安定依存の法則にあるように、安定したモジュールに依存することによって頻繁な変更に対して影響範囲を少なくすることが期待できます。

このルールにより、層をまたいで同一のデータ型にアクセスすることは許されません。横断的に使われるデータでも、影響範囲を狭めるため各層で別々に定義してInterface Adapterで変換します。これによって、外側の仕様変更が内側に影響を与えることがないようにします。

図が言わんとしていることがイメージできたでしょうか。大雑把に言えば、やりたいのは「関心の分離」や「安定依存」といったプラクティスをアーキテクチャの枠組みによって実現することです。

iOSアプリにおける実装

チームで現状採用している具体的な実装例を見ていきます。 コード量が多いため、全体の実装はGithubに上げつつ、この記事では軸にしている考え方について解説します。


Github - a-beco/iOS-CleanArchitecture-Example

あくまでも一例なのでバリエーションはあるでしょうし、まだまだ詰めきれていない部分もありますが、クラスの分け方や依存関係など、これからClean Architectureを導入しようとしている方々の参考になれば幸いです。

今回構成を決めるにあたって、こちらの記事が具体例としてとても参考になりました。ありがとうございます!

まだMVC,MVP,MVVMで消耗してるの? iOS Clean Architectureについて - Qiita

ただし、依存の方向や命名などは色々と再考しています。また、今回用意したものはアーキテクチャの構成を理解するための単なるスケルトンであり、こちらの記事のように動作的に意味のあるサンプルアプリではありませんのでご容赦ください。ライブラリは一切使わず、伝統的なdelegateパターンのみで実装しています。

それでは大事なポイントについて解説していきます。

3つのグループ分け

f:id:a_beco:20161212013114p:plain 図のように大きくPresentation, Domain, Dataという3つのグループに分けています。ディレクトリ構成もこの分け方に従っています。ここで、この3つはあくまでもグループ分けであり、層ではないということに注意してください。Clean Architectureは円形で、図の同じ色の部分が同じ層になります。

Presentation

Presentationは見た目にかかわる部分を含みます。いわゆるビューです。見た目を描画するだけでなく、タッチイベントなどのユーザアクションを検知し、ドメインロジックを呼び出します。

ビューに関するロジックはビジネスロジックには含まれないためPresentationに書かれます。UIViewControllerはビューのライフサイクルイベントをハンドリングするためPresentationの一部として考えています。iOSアプリを書く場合、UIViewControllerがエントリポイントとなるためビューを中心に考えてしまいがちですが、これらはClean Architectureにおいては外側の存在であり、あくまでもDomainが中心的な存在となります。

Interface AdapterについてはPresenterとControllerに分けています。これらはどちらも単にデータを変換して渡していくだけのシンプルな実装であり、ControllerがUseCaseへの入力、PresenterがViewへの出力を担っています。2つに分けているのは、UIViewControllerからUseCaseへ、またその逆へと通り抜けていくことを強制するためです。ここの役割はデータ変換であり、それ以上のロジックは持ちません。PresentationにおいてはUseCaseを複数使う場合などUseCaseから返ってきた処理をこの層ですぐにUseCaseに返したくなってしまうことが多いかもしれないと考えたため、入口と出口を分けることにしました。

ただし、この構成はClean Architectureにおいて特にルールとして言及があるわけではないことと、ドメインで同期的な処理をしたい場合に冗長な記述になってしまうため、考え直すか例外を認める必要があるかもしれないと感じています。

Domain

アプリのドメイン部分です。ビジネスロジックを集約します。ビジネスロジックとは、このアプリが提供する機能です。特定の画面でしか使わないものはありえますが、特定の画面と密に紐付くものではありません。PresentationとDomainは多対多の関係です。1つの画面が複数のUseCaseを持ち得るし、1つのUseCaseを複数の画面から使いまわすこともあり得ます。

DomainはPresentationのロジックやDataの取得先の変更に対して全く依存しないように作ることが理想的です。

UseCaseクラスの粒度をどうするかは難しいところですが、例えば認証についてはAuthUseCaseにまとめ、Presentationからは単にloginやlogoutを呼び出すようにする、などが考えられます。ログイン処理の詳細についてはDomainに隠蔽されます。

ボブおじさんの元記事ではEntityはEnterprise wideなビジネスルールを、UseCaseにはApplication specificなビジネスルールを書く、という役割分担がなされています。Entityのほうがより業界的に一般的なルールを書くということかと思うのですが、単に「データを取ってきて表示する」みたいな単純な処理だとEntityに当たる処理が無いことも多いのではないでしょうか。

Data

Domainからアクセスされ、外部データにアクセスします。取得・操作するデータの実体を抽象化します。例えばデータベースなのか、外部サーバなのか、ローカルキャッシュなのか、などはDomainから意識しないものになっている必要があります。例えばDomainからはgetUserDataのようにするだけでどこからかデータが取れる、といったイメージです。

認証機能などデータアクセスとして捉えるか難しいものもありますが、外部サーバにアクセスするものは現状Dataにまとめてしまっています。

所有関係・依存関係

f:id:a_beco:20161212040820p:plain ボブおじさんの元記事でも言及されていますが、所有関係と依存関係は明確に区別して考える必要があります。

呼び出すものは呼び出されるものを参照する必要があります。図のデータの流れの通り、ControllerはUseCaseを所有し、UseCaseはGatewayを所有しています。しかし、Clean Architectureの重要なルールとして依存関係は内側に向かわなければなりません。この問題は依存性逆転の原則に基づいてprotocolを円の内側に置くことで所有関係と依存関係を逆転させて解決しています。イメージとしては、円の内側にいるものが外側にいるものに対してprotocolでインタフェースを定義し、実装を要求するような形です。詳しくはソースコードを参照してもらうとよいかと思います。

Clean Architectureから外れたもの

定数や横断的に使うクラスについてはClean Architectureに含めないようにしています。例えばサンプルにはありませんが、DIコンテナやLoggerなどはアプリ全体で使うためそれに当たります。

また、開発中のアプリでは画像のロードにKingfisherを利用しています。このライブラリではUIImageViewのextensionとして画像のURLを渡すとロードして表示する機能が提供されていますが、Clean Architecture的には画像の取得は外部からのデータ取得なのでDataに置くべき処理と考えられます。Kingfisherでは画像のダウンロードのみを行うこともできるため、そのように作ることもできますが、工数がかかりすぎるためこれについては利便性をとり、Presentationで画像をロードするようにしています。

これによってアプリがライブラリの作りに強く依存してしまったため、画像ロード部分を別なライブラリで置き換えるのは難しくなります。これはリスクですが、実装コストは節約できました。採用するライブラリによってはこのような判断を迫られることも現実問題としてあるかと思います。

メリット・デメリット

開発が進んできて感じているメリットとデメリットについてご紹介します。

メリット

テスタビリティが向上する

クラスが疎結合かつ小さな単位で分割されることがほぼ強制されるため、モデルのかなりの部分がスタブを用いてユニットテストを書きやすい構成になります。ビューに関してもきちんと書いていれば純粋なビューロジックのみになるため、XcodeのUI Testを利用することでデグレやすい部分のテストなど自動化しやすいのではないでしょうか。

サーバ実装ができていない部分に関してモックで代替するような実装も簡単にできます。

再利用性が向上する

今回製作中のアプリがApp Extensionを利用するものなため、感覚的には2つのアプリを作成しているような感じなのですが、ビューは異なりつつも多くの部分のロジックが共通していました。その一方で認証やデータの取得先など部分的に異なる箇所があったのですが、Clean Architectureを採用していたことで異なる部分のみをコンポーネントとして作るだけで済み、手戻りなくスムーズに実装できたことで時間を節約できたように思います。

もし何も考えずに作っていれば同じ処理の実装を共通化できなかったり、不格好なマクロで分岐させる必要があったかもしれません。

機能を置き換えやすくなる

再利用性でも少し触れましたが、DBを別なものに置き換えたり、サーバから取ってきていたものをローカルから取るようにしたりなど、一部分を置き換えるのが楽です。

見通しが良くなる

変更するときの影響範囲がわかり易いです。円の外側の変更が内側にまで影響することはありません。これはビューの見た目やデータの取り方などが変わってもドメインの処理には影響しない、ということです。以前のプロジェクトでは依存が連鎖して思いがけない箇所に依存したことで問題になったケースもありましたが、今回は心配なさそうだなと思います。

デメリット

学習コストが高い

シンプルなMVCなどのアーキテクチャと比べると理解すべきことが多く、慣れないうちは読み書きするのが難しいようです。パッと見たときにファイルが多くて複雑に感じてしまいがちなのもあるかもしれません。複数人で開発する場合、チーム内で共通認識を持って開発していくためにドキュメント化や勉強会の開催などの布教活動が必要になるかもしれません。

面倒なことが多い

ちょっとあのデータにアクセスしたい、といったときにいちいちInterface Adapterを用意して。。というのはなかなか手間です。このような面倒な手間も結果としては良いプラクティスであることが多いように思いますが、養成ギプスのような痛みを伴うのも事実です。

何度も心が折れそうになり、「このへんは例外的にグローバルアクセスできてもいいかな。。」などの特に根拠もない悪魔の囁きに耳を傾けかけたこともありましたが、踏みとどまって面倒な手順を踏むことで見通しは良くなり、スムーズな再利用に繋がって良かったとは思っています。

逆にここを妥協するとアーキテクチャの恩恵が薄れてしまうので、なあなあにならないよう注意したいところです。

ライブラリやSDKの設計思想と食い違う

前述したKingfisherの例もそうですが、ライブラリの設計思想がアーキテクチャにそぐわないことは多々あるかと思います。

例えば、RealmからデータをFetchすると得られるObjectは、複数箇所のビューで同じデータを使っていてもObjectを持たせておくことで自動的にすべて同期されるという便利な特徴があります。

しかしこれを活かそうとするとDataからPresentationまで広範囲に依存するデータ型を作ることになってしまいます。最終的にこれはClean Architectureの文脈に沿わないと判断し、Realmは単なるデータストアとして使うことにしました。

また、画像処理をEntityでやりたいと思っても、使いたいライブラリがUIと密に結びついていると難しいだとか、ライブラリで定義されているデータ型を他の処理で使いたいと思ったときに層をまたぐデータを作ってしまってClean Architectureのルールに反してしまうといったこともありえます。

ライブラリ導入時にはそのような不整合をどうするかについて悩まされることになるかもしれません。

最後に

Clean Architectureについて、実例を交えながらメリット・デメリットなど解説させていただきましたが、いかがでしたでしょうか。

僕はClean Architectureに関して一言で表現するならば様々な経験則に基づいた「Modelの細分化」なのかなと考えています。アーキテクチャの歴史はModelとViewを分離することからはじまり、MVCやMVP,MVVMなどModelとViewをつなぐ部分で様々な提案がなされてきています。Clean ArchitectureではそのうちのModelを小さく明確な役割によって分割しつつ、それぞれを疎に結合し、影響範囲のスコープを狭めることで心配事を分割し、変更に強い柔軟なつくりを実現しようとしているのかなと。

一方で、近年インタラクティブな演出やシームレスなアニメーションなどのリッチ化が進むビューの設計に関してはClean Architectureはせいぜいビジネスロジックの混在を防ぐのに留まっており、なんの指針も与えていません。ビューは複雑化の一途を辿っており、実際のところ、現在のプロジェクトで一番行数が多く複雑なクラスはビューコンポーネントです。特にゲームなどビューロジックがファットになるものに関してはさらなるアイディアが必要になるでしょう。

この世界に銀の弾丸はなかなか見つかりませんが、Clean Architectureのようなアイディアは我々がソフトウェアが本質的に持つ複雑性と戦う上で強力な武器となりうる、と信じたいです。

Conforming to these simple rules is not hard, and will save you a lot of headaches going forward.

引用: The Clean Architecture | 8th Light


何かの参考になれば幸いです。

参考文献