読者です 読者をやめる 読者になる 読者になる

feather for Twitter の構造

feather

今日は feather のソースコードレベルでのプロジェクト構造を紹介します。

feather のプロジェクトのグループ構造は下の画像のようになっています。

スクリーンショット 2014-04-13 10.18.38

大きく以下の4つのグループがあります。

  • Controller
  • View
  • Service
  • Helper

それぞれについて説明します。

Controller

UIViewController のサブクラスが配置されているグループです。

ViewController は画面を制御しているもので、1つの画面に対して1つ以上の ViewController があります。

例えばタイムラインの画面、ツイートを投稿する画面、設定画面などがありそれぞれに対して ViewController があります。

画面ごとに名前を付けてグループを作っています。

|- Controller 
    |- HogeController
        |- HogeController.h
        |- HogeController.m
    |- PiyoController
        |- PiyoController.h
        |- PiyoController.m

という感じです。

View

UIView のサブクラスが配置されているグループです。

つまり UI パーツ(View)のソースコードが配置されています。

例えば、ボタンや、スイッチ、タイムラインのグルグル回る View などがあります。

ここに配置されているのは複数の画面で共通して使用される View です。

特定の ViewController でしか使用しない View は その ViewController が配置されているグループの中に View というグループを切って配置しています。

|- Controller 
    |- HogeController
        |- HogeController.h
        |- HogeController.m
        |- View
            |- HogeRootView.h
            |- HogeRootView.m
    |- PiyoController

という感じです。

Service

Service は feather を作り上げている根幹となるクラス群です。

先に紹介した Controller は Service が提供する機能を使用しています。

例えば タイムラインを取得する、取得した画像をキャッシュする、エラーが発生していないか監視するなど様々な Service があり、それぞれがクラスになっています。

例えば "タイムラインを表示する" という処理の流れは以下のようになります.

※ ここでは タイムラインを表示する ViewController を TimelineViewController、タイムラインの取得を行う Service を TimelineManager とします。

  1. ユーザの操作などでタイムラインの取得が必要になる
  2. TimelineViewControllerTimelineManager にタイムライン取得の指示を出す
  3. 指示を受けた TimelineManager がタイムラインを取得する
  4. 取得完了したら TimelineManager が成功 or 失敗を通知する(NSNotification)
  5. TimelineViewController がタイムライン取得の成功 or 失敗通知を受け取る
  6. TimelineViewController が UI の更新を行う
  7. 新しいタイムラインが画面に表示される

このようにして ViewController の仕事を Service への指示とその結果の反映だけにして、コードの肥大化を防いでいます。

また、これらの Service は沢山の種類があり、アプリケーション上に1つだけ存在するべきもの(シングルトン)やアカウント毎に1つずつ存在すべきものがあります。

※ feather for Twitter はマルチアカウント対応の Twitter クライアントです。

例えば、アプリケーションの設定を提供する Service は1つだけ存在すべきですが、アカウント毎の設定はアカウント毎に1つずつ存在すべきです。

こういった問題を解決するために feather では Service Locator パターンというものを使用しました。

これについては長くなるのでここでは説明しません。

テストのしやすさから Objective-C でよくある sharedManager の様なシングルトンの使用は基本的に禁止しています。理由としてはシングルトンオブジェクトが内部で別のシングルトンオブジェクトを使用している場合、テストが非常に難しくなるからです。

Service が他の Service を参照する場合は全てイニシャライザで依存性を解決する方針にしています。

微妙なところもありますが、今のところこの方針で上手く行っている感じがします。なによりも、プロジェクト全体がこの方針で統一されているので見通しが良いです。

この辺りに関しては更に開発を進めていく中でどうなるか楽しみです。

Helper

カテゴリや便利マクロの集合です。

どこからでも使う可能性があるカテゴリなどを置いています。

例えば NSString のカテゴリなどです。

特定の UI や Service に密結合なカテゴリはそのグループの中に Helper というディレクトリを切って配置しています。

まとめ

ということで 4つの大きなグループを紹介してきました。

これらを全体で表すと以下の様な Tree になります。

|- Controller 
    |- HogeController
        |- HogeController.h
        |- HogeController.m
        |- View
            |- HogeRootView.h
            |- HogeRootView.m
        |- Helper
            |- HogeController+BenrinaCategory.h
            |- HogeController+BenrinaCategory.m
    |- PiyoController
|- View
    |- Button
            |- CoolNaButton.h
            |- CoolNaButton.m
    |- Switch
            |- KakkoiiSwitch.h
            |- KakkoiiSwitch.m
|- Service
   |- Timeline
       |- TimelineService.h
       |- TimelineService.m
       |- Storage
           |- TimelineCacheStorage.h
           |- TimelineCacheStorage.m
           |- Helper
               |- TimelineCacheStorage+KashikoiCategory.h
               |- TimelineCacheStorage+KashikoiCategory.m
       |- Helper
           |- TimelineService+KigakikuCategory.h
           |- TimelineService+KigakikuCategory.m
   |- ServiceLocator
       |- ServiceLocator.h
       |- ServiceLocator.m
|- Helper
     |- String
         |- NSString+MinnagaTukaeruCategory.h
         |- NSString+MinnagaTukaeruCategory.m
         |- NSString+SugokuBenrinaCategory.h
         |- NSString+SugokuBenrinaCategory.m

feather にはテストコードもあるのですが、テストコードが配置されているディレクトリもほぼ同じ Tree 構造になっています。

現在 ** 個のソースファイルがあり、そのコード行数は ** 行程度ありますが、今のところ誰も触れないほど怪しいコードは存在していません。

開発当初からソースコードの構造設計をちゃんとしたことと、テストコードの作成、コードレビューの実施を行ってきたことが間違っていなかったことを証明してくれいてる気がします。

これからも、プロジェクトの品質を落とさないように取り組んでいきたいと思います。