builderscon iOSアプリの簡易説明

先日、スタッフとして関わっているbuildersconiOSアプリをリリースしました!🎉

blog.builderscon.io

本エントリで簡易的なアプリの紹介と構成の紹介を行います。

追記

この紹介は v1.0.0 時点でのアプリ構成を説明しており、
最新版では構造など更新が入っています。

機能

f:id:to4iki:20170801233159p:plain

  • 複数日付のタイムテーブル表示
  • フロアマップ表示
  • QRコードーリーダーの搭載

構成/設計

project構成はDDDのレイヤードアーキテクチャをベースに、
関心毎の分離(描画処理と業務ロジック)ができるよう下記のような構成にしています。

tech.recruit-mp.co.jp

── AppDelegate.swift
├── data
│   ├── dataSource
│   │   └── SessionDataSource.swift
│   ├── extension
│   │   ├── CollectionExtension.swift
│   ├── storage
│   │   ├── DiskCache.swift
├── domain
│   ├── repository
│   │   └── SessionRepository.swift
│   ├── translater
│   │   ├── TimetableTranslater.swift
│   └── usecase
│       └── TimetableUseCase.swift
├── environment
│   ├── Config.swift
└── presentation
    ├── view
    │   ├── Timetable.storyboard
    ├── viewController
    │   └── TimetableViewController.swift
    └── viewModel
        ├── Timetable.swift

当初はレイヤー毎の責務分離がはっきりしている + テスタビリティが高いClean Architectureの導入を考えていたのですが、

  • 現状、そこまで複雑なアプリになりえない想定だったのでオーバースペック気味
  • とにかく時間がなかったので開発速度優先
  • 但し、責務分離の観点で費用対効果が高そうなクラス分けは取り入れる

上記3点の理由から一部取り入れています。

描画

複数日付のタイムテーブルの表示には、下記2つのライブラリを使用しています。

セル結合部分はとても泥臭い実装をしているので、リファクタリングしたいところ。

ネットワーク関連

buildersconにはAPIが用意されています

github.com

JSON Hyper-Schemaに対応しているので、これに沿った形式でSwiftのマッピング用のクラスを生成できればよかったのですが、最初は必要な項目も限られるし自分で定義した方が早いなと判断し自前でクライアントを作成しました。

github.com

クライアントはコールバックベースで完了時の処理を実装しているのですが、
ネストが深くなってきた + テストが書きづらいという理由から、呼び出し側で非同期オブジェクトを返すような形式にラップしたいなと考えております。#61

キャッシュ

当初よりオフライン及びキャッシュ対応はマストで入れようと決めていたので、下記の方針で実装しています。

  • 初回アプリインストール後はディスクに書き込んだデータを使用
  • Repositoryの実装で、ディスクから取得するかAPI経由で取得するかを隠蔽(API経由で取得した場合はディスクに書き込み)
  • 起動時にAPI経由で最新のデータを読み込みバックグラウンドでディスクに書き込んでおく

3つめはpull to refresh(引っ張って更新)でデータ再取得/再描画すべきだとは思いつ、初回バージョンでは富豪的APIを叩くようにしています。

Repositoryの実装はこんな感じ

func find(completion: @escaping (Result<Conference, RepositoryError>) -> Void) {
    localDataSource.find { localResult in
        if case .success(let value) = localResult {
            completion(.success(value))
        } else {
            self.remoteDataSource.find { remoteResult in
                switch remoteResult {
                case .success(let value):
                    completion(.success(value))
                    self.localDataSource.store(value) { writed in
                        if case .failure(let error) = writed {
                            log.error("store error: \(error.localizedDescription)")
                        }
                    }
                case .failure(let error):
                    completion(.failure(.find(error)))
                }
            }
        }
    }
}

うう、ネストが深い。。

また、APIのtokenを公開できない都合上、普段開発するDebugビルドの際にはプロジェクトに埋め込んだFileからデータを読み込むようにしています。

開発手順

セットアップ

リポジトリは個人アカウント配下にありますが、ゆくゆくはbuilderscon配下に移動予定です。

github.com

READMEにも書いてありますが、

  1. Ruby製のツール(fastlane..)を幾つか使用しているので、Bundlerでローカルにgemの依存をインストール。*1

    $ bundle install --path vendor/bundle

  2. iOS/Swift関連の依存ライブラリをCarthageを使ってインストール。

    $ carthage bootstrap --platform iOS

  3. (任意)SwiftLintをローカルマシンにインストール。

ビルド

シミュレーターで開発する分には、Xcodeでprojectを立ち上げProduct > Run(⌘R)で実行可能です。
実機に転送したい場合は、Xcode8.xでのiPhone実機デバッグメモ - Qiitaを参考に、projectファイル内のSigning/Teamを自分の登録してあるAppleIDに置き換え試してみてください。

まとめ

ざっくりとアプリの全体像を説明しました。
まだまだ追加したい機能やbugfixもあります(Issues)ので、ご興味のある方はPRを送ってくれると嬉しいです!

そして皆さん! 8/3, 4, 5にbuilderscon tokyo 2017が開催されます!
参加する方は、是非アプリをインストールし使ってみてくださいー!😄

*1:現時点ではマストではないので、1はスキップ可能