こんにちは。

サイバーエージェント子会社の株式会社トルテでiOS開発してる@corin8823です。

2017年3月1日にiOSエンジニアのための勉強会、CA.swift #2を開催しました。

CA.swift

前回の勉強会の様子はこちらで確認できますCA.swift #1 レポート

登壇者として参加したのでその発表の内容 + αを記載したいと思います。

チームで新規アプリを作る際の参考になれば幸いです。 当日の資料はこちらです。

当日の資料

導入

昨年の2016年11月にマッチングアプリ開発のため、新しい子会社株式会社トルテが設立さました

そこで新規サービスを開発しています。

そこで今回はそのサービスを開発するにあたり、チームのメンバーと本開発に入る前になにを決めたかをお話できればと思います。 今回は主に他の職種の技術者と決めたことに焦点をあてて話します。

サーバーサイドエンジニア

サーバーサイドエンジニアと決めなければいけないことは、基本的にはAPIのリクエスト、レスポンスだと思います。 今回はそのAPIドキュメント用のツールの話をしたいと思います。 チームの中で、出た要件は下記のようなものでした。

要件

  • APIのリクエストとレスポンス記述
  • 人数も少ないのでメンテナンスコストはなるべく下げたい
  • 開発当初はモックサーバーとして叩きたい
  • 動いてるコードと同期しやすい方が良い

APIドキュメントは何も考えず、WikiやDocsにまとめると、すぐに実装とかけ離れたものになりがちです。

  • ドキュメントが古い
  • ドキュメントにしかないものがある

等々あり、なるべくメンテンスコストを下げるツールを使いたくなります。

さらにサーバーサイドもアプリも1からの開発なので、APIの完成とアプリ側の画面の作成に時期的にズレが出てきます。

そのズレをなるべく開発に影響をあたえないようにするために ドキュメントがモックサーバーとして動く要件は開発スピートを上げるためには、必要不可欠でした。

そこで今回トルテが導入したのはApiaryです。

ApiaryとはAPI Blueprintを利用してAPI仕様をウェブ上で編集、管理するためのAPIドキュメント作成ツールです。

書いたドキュメントが手軽にモックサーバとしても動くものです。最近オラクルに買収されました。

Swaggerとも比較したのですが、開発初期段階でモックサーバーとして叩くには、 環境構築に時間がかかりそうだったので少なくともリリースするまではApiaryで行くことを決めました。

ApiaryはGitHub連携ができるので、PullRequestベースでドキュメントをメンテンスできます。

API仕様の変更やレスポンスの変更も基本的にはGitHub上で行うことができます。

コミットログとして変更履歴も残るので、後からJoinした方にも歴史が伝わりやすいかなとも考えています。

という感じで進めていこうということで開発に入りました。

Andoridエンジニア

Andoridエンジニアと決めなければいけないことは、特にないことが多いです。

しかし、今回は少人数ということもあり設計を合わせたかったのでその話をしたいと思います。

要件というか重視したこと

  • Androidと設計を合わせたい(レビューや相談がしたい)
  • 状態管理が多くなっても辛くない設計

まず、個人的に一番重要視したのは、Androidと設計を合わせることです。

各々1人ずつしかいないので、難しいところやのちに品質に差が出るようなことはなるべくしたくないです。

要するにある程度レビューをできる形にして、属人性を下げながら複雑にならないようにしたかったです。

もう一つとしては状態管理が多くなっても辛くないような設計 「ViewやViewControllerでなるべく状態を持たないようにする」とかそういう事を考えてました。

引用:https://facebook.github.io/flux/docs/in-depth-overview.html#content

AbemaTVでの採用実績もあったので、最終的にはFluxでいくことに決めました。

Flux自体の細かい説明は省きますが、 データの流れを制限して、状態管理をStoreに押し込めることで、View側で状態管理を持たなくてよくStoreに変更があったら、ViewやViewControllerが反応するようなコードを宣言的に記述できます。

簡単にサンプルを用いて説明します。

下記はなにかのAPIを叩いて、UITableViewにリスト表示するまでのコード例です。

class TopAction {
    enum Item {
        case load
        case error
    }
    private let dispatcher: Dispatcher

    init(dispatcher: Dispatcher = .shared) {
        self.dispatcher = dispatcher
    }

    func loadItem() {
        let request = API.ItemRequest()
        Session.send(requeset) { result in
            switch result {
            case .success(let items):
                self.dispatcher.dispatch(obj: items, key: Item.load)
            case .failure(let error):
                self.dispatcher.dispatch(obj: error, key: Item.error)
            }
        }
    }
}

loadItem()を呼ぶとActionがAPICallして結果のresultDispatcherdipatchします。

ここのDispatcherがやってるいることはenumのkey値とvalueが登録されているobjectにここでいうitemserrorを伝える(渡す)役割をしています。

class TopStore {
    private(set) var items = Variable<[Item]>([])
    private(set) var error = PublishSubject<Error>()

    init(dispatcher: Dispatcher = .shared) {
        dispatcher.register(observer: self, key: TopAction.Item.load) { [weak self] (items: [Item]) in
            self?.items.value = items
        }

        dispatcher.register(observer: self, key: TopAction.Item.error) { [weak self] (error: Error) in
            self?.error.on(.next(error))
        }
    }
}

Store側では自分自身をあらかじめDispatcherに登録しておき、enumのkey値とクロージャの型が一致したときに受け取れるようにします。

そこでStore自分自身のプロパティにDispatcherから来た値をセットし、

class TopViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    private let store = TopStore()
    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.store.error
            .bindNext { error in
                // error handling
            }
            .addDisposableTo(self.disposeBag)
        self.store.items
            .asObservable()
            .bindNext { [weak self] _ in
                self?.tableView.reloadData()
            }
            .addDisposableTo(self.disposeBag)        
        TopAction().loadItem()
    }
}

ViewController側で宣言的に書いていた、クロージャの中が実行されるという流れになります。

このようにすることで、ViewやViewControlerはStoreを保持するだけで、Storeの中身のプロパティの変更に対して宣言的に記述できるので、 View側で状態やフラグを持たずに済むのでバグは生みづらくなるのではないかという考え方です。

Androidも同じFluxにしておくことで、Fluxで悩みがちなどこまでStoreで管理するか?、どのようにActionを切り分けていくか?が相談やレビューができるので、1人で考えるよりは品質を高めることができると思っています。

デザイナー

デザイナーと決めなければいけないことは、Resources周りだと思います。

ここでは、Sketch、Font Icon、Color周りの話をします。

Sketch

最近ではデファクトになりつつあるSketchでやり取りをしています。

Sketchでのやりとりはマージンやフォント、カラー指定、画像の書き出しなどの待つ時間をなくし、スピード感高く開発を進めることができます。

デザイナーにもGitHubにSketchファイルをpushしてもらっているので、もう少し開発が進んだらgit-sketch-plugin等のDiffがわかるプラグインを用いて変更箇所が追いやすいようにしたいです。

フォントアイコン

トルテではアイコン系の画像はフォントアイコンにしています。 フォントアイコンのメリットは多く

  • 画像と違い色やサイズを無視して一つ作ればよい
  • Aondroidにも展開できる
  • アイコン + 文字のViewもUILabelもNSAttributedStringを用いて作りやすい

詳しくはわかりませんが、トルテではGlyphsを用いてiconを作ってもらっています。

一つだけ注意する点として、作るアプリで使用してるフォントに合わせてアイコンフォントをつくってもらうことです。

icon_font

ここに2つの編集ボタンがあります。

右側のほうがアイコンが少し下にずれているつもりで作っています(笑) こういうズレをアプリ側で解決しないように決めています。

具体的にはNSBaselineOffsetAttributeName等を使ってベースラインをいじることで調整することはしないようにしました。

場所によってコードで位置をいじったりすると、デザインレビューの時にたいてい修正がはいってしまいます。

アイコンフォントは使用しているフォントに合わせて作成してもらうことで余計なコストを下げようとしています。

色もどのように定義するかでデザイナーやAndroidエンジニアとのコミュニケーションのコストが大きく変わます。

サービスブランドの整合性担保しつつ、サービスカラーやフォントカラーの変更に耐えられるようにしながらスタイルガイドを作り開発効率アップを満たすことが重要だと考えています。

しっかりと定義してないと

  • 定義されてない色の追加を止める
  • 色の意図が汲み取れず、実装時によく間違えちゃう

等の細かいコミュニケーションコストが発生することが多いと思います。

今回はフロントエンド視点:デザイナーと協業して作るスタイルガイドの難易度を下げるの記事を参考に明パターンと暗パターンの箱を用意してもらい、それに合わせて定義しています。

(画像は参考例)

紹介した記事でも書いてあったように、定義されてない状態も良しとして、作り込みすぎないようにしてもらいました。

最近導入できたので、効果やより良い案があったらまたどこかで紹介したいと思います。

実装例は下記に乗せておきます。 StructでPaletteカラーを定義しておき、それを参照用の変数から取得するという感じにしています。

    public static var lightAccent: UIColor {
        return Color.pink.color
    }
    public static var lightDanger: UIColor {
        return Color.red.color
    }
    public static var lightBase: UIColor {
        return Color.grayF6.color
    }
    public static var lightText1: UIColor {
        return Color.gray30.color
    }
    public static var lightText2: UIColor {
        return Color.gray66.color
    }
    public static var lightText3: UIColor {
        return Color.grayAA.color
    }
}

iOSエンジニア

時間がなくて発表できなかったのですが、開発する前に決めたおいた方が良いなと思ったこと中心にiOSの話を最後にします。

Embedded Frameworkの導入

Embedded FrameworkはiOS8で使えるようになったもので導入することで何点かメリットがあります

  • ビルドパフォーマンスが向上する
  • 依存関係が強制されてシンプルになる

ビルドパフォーマンス

swiftも3になりswift1.1のときなどに比べたらビルドは早くなりましたが、それでも速い分に越したことはありません。 メインターゲットの実行時に差分ビルドが効きやすいです。

依存関係が強制されてシンプルになる

フレームワーク化したライブラリ側からはメインターゲットを参照できないので、設計をある程度強制させることができます。

Embedded Frameworkは開発が進んでからだと導入しづらいので、はじめから入れることを決めて実装を開始しました。 今回は下記のような分け方をしましたが、Flux周りを切り出すのもありかなぁと感じております。

  • ○○Kit: APIClientやエンティティの定義
  • ○○UI: UIのLibraryっぽいものをおく、○○UIDemoとセットで実装しておくと、細かいアニメーションの調整がしやすい
  • ○○Extension: いわゆるExtension系

まとめ

新規アプリを作り始める前にチームで決めたことはまとめると

  • サーバーサイドエンジニア: API Document
  • Android エンジニア: 設計
  • デザイナー: Sketch, フォントアイコン、色
  • iOS エンジニア: Embedded Frameworkの導入となります。

APIの話をする前にAPIドキュメントがあったほうがよいし 作り始める前に状況をみてアプリ通しで設計を合わせた方がいいケースもある。 デザインの細かい話をする前に、どのような管理でやっていくかをあらかじめ決めておいたほうがよいな考えてます。

長くなりましたが、ユーザーのためにより良い開発ができるように切磋琢磨していきます。

corin8823
2013年度新卒入社。iOSとサーバーサイドをどちらも行き来してる感じです。温泉とか好きです。