TSUNAGU GROUP TECHNOLOGIES

TGT TechBlogTGT TechBlog

フロントエンドからバックエンドまでの技術ナレッジ

インディバルのiOSアプリ開発の舞台裏

こんにちは、アプリ推進室室長をしてますHです。ちなみに新婚です。
弊社でも、社内の取り組みや会社の雰囲気が伝わる情報を発信するため、月一で、エンジニアブログを開設することになりました。
第一回目となる今回は、弊社のiOSアプリ開発の話をさせていただこうと思います。

チーム構成

アプリ推進室では、以下の4名で開発をしています。

  • ・iOSエンジニア:1名(私)
  • ・Androidエンジニア:2名
  • ・デザイナー:1名

私自身はチームのマネージャなので、マネジメント業務も仕事の一つですが、少数チームということもあり、開発業務もこなしています。
iOSアプリは、開発途中のものを含め、6つ有ります。
他の部署のエンジニアの力を借りながら作っていますので、すべて私が作ったアプリもあれば、コードレビューをしただけというアプリもあります。
アプリチームが立ち上がって、1年と3ヶ月ほど経ち、Swiftの進化とともに、私達のプログラミング能力も進化していってると感じています。
以下では、現在手がけているアプリについて、どのような点に注力し開発しているのかを紹介したいと思います。

開発方針

1.使っているライブラリ

まずは、現在使っている(今後使う予定含む)ライブラリを、列挙していきます。
* RxSwift
* Realm
* SVProgressHUD
* ObjectMapper
* IBAnimatable
* M13Checkbox
* GoogleAnalytics
* Firebase
* crashlyticsとfabric

(1)ObjectMapperとRxSwift

これら2つを使って、nsurlsessionで取得したものをobjectmapperを使って扱いやすくしました。
genericsを使って、どういったデータが来ても、同一処理で扱えるようにしました。
上記は、TL社さんと勉強会(不定期ですが社外の方と交流をしています)をした際に、こういったことをやってるよ~というのを聞いたので、真似てみました。

//
//  HttpClient.swift
//  xxxxxxxx
//
//  Created by xx xx on 2016/06/14.
//  Copyright © 2016年 xx xx. All rights reserved.
//
import Foundation
import HogeFramework
import ObjectMapper
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
final class HttpClient<C:Mappable>{
    /**
     通信をしてデータを取得する
     - returns: <#return value description#>
     */
    func getData(request:NSURLRequest) -> Observable<(C?,NSHTTPURLResponse)>{
        return Observable.create { observer in
            var d: NSDate?
            if Logging.URLRequests(request) {
                d = NSDate()
            }
            let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
                if Logging.URLRequests(request) {
                    let interval = NSDate().timeIntervalSinceDate(d ?? NSDate())
                    //                    print(convertURLRequestToCurlCommand(request))
                    //                    print(convertResponseToString(data, response, error, interval))
                }
                guard let response = response, data = data else {
                    ColorLogger.defaultInstance?.error(error)
                    observer.on(.Error(error ?? RxCocoaURLError.Unknown))
                    return
                }
                guard let httpResponse = response as? NSHTTPURLResponse else {
                    observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
                    return
                }
                do{
                    let dict = try NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.AllowFragments)
                    let countObj = Mapper<C>().map(dict)
                    observer.on(.Next(countObj,httpResponse))
                    observer.on(.Completed)
                }catch let error{
                    ColorLogger.defaultInstance?.error("\(error)")
                    observer.on(.Error(ApiError.FormatError))
                    return
                }
            }
            let t = task
            t.resume()
            return AnonymousDisposable{task.cancel()}
        }
    }
}

呼び出し元は以下になってます。

let request = NSURLRequest(URL: url)
        let client = HttpClient<HogeData>()
        return client.getData(request)
            .observeOn(Dependencies.sharedDependencies.backgroundWorkScheduler)
            .catchError{(error) -> Observable<(HogeData?, NSHTTPURLResponse)> in
                return Observable.just((nil,NSHTTPURLResponse(URL: url, statusCode: Const.HTTP_RESPONSE.HTTP_STATUS_CODE_UNKNOWN, HTTPVersion: nil, headerFields: nil)!))
            }
            .map { hogeObj,httpResponse in
                if httpResponse.statusCode != Const.HTTP_RESPONSE.HTTP_STATUS_CODE_OK {
                    throw ApiError.Bad
                }
                return hogeObj
            }
            .observeOn(Dependencies.sharedDependencies.mainScheduler)

(2)GoogleAnalyticsと、Firebase

GoogleAnalyticsは主にユーザの傾向を把握するために使っています。Firebaseもいま作ってるアプリで試しに組み込んでみようと考えています。

2.開発における決まり事

大げさに書いてますが、以下のような決まり事にしています。

(1)storyboardは画面遷移として使い、各頁のレイアウトはxibファイルを使う

以下を参考にさせていただきました。
http://techlife.cookpad.com/entry/2015/06/24/190546
実際は以下のようにstoryboardは構成されています。

img_storyboard

(2)コーディングルールを用意

主にはgithubのSwiftコーディングルールを参考にさせていただきました。
https://github.com/github/swift-style-guide

(3)Protocol-Oriented-Programing

wwdc2015で提唱された、Protocolベースのプログラミングを意識して使っています。protocolとgenericsを使い、疎結合、クラスの再利用を意識して作っています。
色々資料は見たのですが、以下2つは理解しやすかったと思います。
https://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2
https://www.youtube.com/watch?v=VjVr2ZmJDkc

(4)社内独自フレームワークの作成

フレームワークというと大げさかもしれませんが、CocoaTouchFrameworkを作り、弊社アプリでよく使いそうな機能をまとめ、今後の開発効率を上げようとしています。

(5)開発と制作の作業がぶつからないようにするために

私達のチームのデザイナーはSwiftもxibも触るので、開発の終盤になると、デザイナーと開発者が同時に作業します。そうなると、作業がぶつかって、conflictが発生することが多々あります。そいうった事態を避けるために、泥臭いですが、以下の工夫をしています。
■Color情報は別ファイルにまとめる
AndroidのエンジニアにiOSアプリの開発をお願いした際に、Androidのcolor.xmlみたいなファイルをiOSでも用意して、開発してました。(ColorConstatns.swiftというファイルを作って、そちらに定義をまとめてます)
■CSSファイルみたいなものを用意する
開発と制作の作業がぶつからないように、制作の作業を別ファイルにまとめてしまえばいいのかなと判断しました。
そこで、protocolとextenstionを使い

/**
*  デザイン調整用のprotocol(UIView)
*/
public protocol UIViewUIProtocol: class{
/**
デザイン調整を行う
*/
func designAjustment()
}

を作ります。あとは、UI調整用のファイルを作り

extension IndivalUiView: UIViewUIProtocol{
    /**
     デザイン調整
     */
    public func designAjustment() {
    }
}
public class IndivalUiView: UIView {
    override public func awakeFromNib() {
        super.awakeFromNib()
// Initialization code
        designAjustment()
    }
}

みたいな感じで、実装を書いてもらうようにしてます。これにより、ロード時の画面のデザインをdesignAjustmentに任せることができます。
もちろん欠点もあります。開発初期段階の準備が必要なのと、画面中の処理での動的の変更には対応してないので、例えば動的な変更の処理は、UI調整用のファイルに書くなど独自の工夫が必要です。

class hogeViewController: UIViewController{
@IBAction func onTap(sender:AnyObject?){
    hogeUI()
    }
}

以下別ファイル

extension hogeViewController{
    func hogeUI(){
    }
}

extensionは便利ですね。

(6)1ファイル200行以内

前職で出会ったJavaのエンジニアの方に、1ファイル200行以内を意識しろと言われたことがあります。6年ほど前ですね。
なかなかそれが出来てなかったのですが、Protocol-Oriented-Programmingを意識してみたところ、最近それが出来るようになってきました。

(7)デザインパターン

デザインパターンも意識してます。よく使うのは、AbstractFactory,Observer,singleton,Delegateあたりでしょうか。
オブジェクト単位で処理を分離するということの大切さを実感しつつ、最近開発をしています。
当たり前ですが、コードレビューは行うようにしてます。

3.その他

(1)テストや、ビルドなど

現在は以下を使うようにしています。
* fastlane
* jenkins
* crashlytics
* Testflight

(2)テストバージョン配布の自動化

img_jenkins_crashlytics

テスト配布の自動化をしており、Jenkins+crashlyticsで構築しています。以前TLさんとアプリ勉強会をさせていただいて、その際にJenkinsでテストバージョン配布構築していて、便利という話を聞いたので、構築しました
イメージは以下です。

* ①〜④の部分は、fastlaneに置き換える予定です。(設定自体は済んでいるので、あとは置き換えるだけ)

勉強会やクラブ活動

定期的に以下の勉強会を行っています。

1.週一でアプリ室の勉強会

隔週で発表するような形にしてまして、ネタは何でもOKという緩い感じでやっています。

2.もくもく会

ただ、ひたすら2時間ほど自分の調べたいことを勉強する会を週1で行ってます。仕事しながらですと、なかなか調べる時間も取れないので、場所を変えて、2時間週一で、調べたいことを調べるようにしています。

3.クラブ活動

業務とは別で自分の好きなモノを作るという時間も週2で設けています。弊社は求人の会社ですが、いま作っているものは求人とは無関係です。ほんとに好き勝手作りたいものを作ってます。

こういった取り組みの効果

正直2〜3ヶ月程度では現れにくいとは思いますが、半年ほど継続すると徐々に出てくると思います。初めて半年以上たった評価のタイミングの際に、参加者から勉強会で学んだことが業務で活かせたという結果も出てきてます。

継続の秘訣

以前某TL社のTさんから「勉強会の継続の秘訣は頑張らないこと」とお聞きしましたが、そのとおりだと思って、頑張らずにやってます。誰か1人でも休んだら開催しないとか普通にあります。 笑

あとがき

いかがでしたでしょうか。
ググれば見つかるような情報だとは思いますが、どれ見たら良いのかなと思った際にこちらが良かった!と言ってもらえれば幸いです。
また、弊社では、iOSアプリも含め、エンジニアが不足しています。私達の会社は、結構自由な働き方が出来るのが売りだと思っています。もし、今回のブログを見て興味を持った方が居ましたら、以下から応募いただけますと幸いです。
https://www.indival.co.jp/employment/

執筆者プロフィール

アプリ室に所属のH氏。東京都出身。個人でアプリ開発をし始めていたのがきっかけで、Indivalに入社。
バドミントンが趣味。最近結婚した。英語力を鍛えるために、スカイプ英会話を毎日している。