MoTLab -GO Inc. Engineering Blog-MoTLab -GO Inc. Engineering Blog-

タクシーアプリ開発で開発効率を上げるためのデバッグメニュー

iOS
November 15, 2021

タクシーアプリ「GO」の iOS アプリを開発をしている古屋です。

この記事では GO でデバッグメニューにどのようなものを実装して開発の効率を上げているか紹介します。


はじめに

デバッグメニューといえば端末をシェイクしたらでてきて、以下のようなものがあるのが一般的かと思います。

  • APIの接続先情報の参照、更新
  • UserDefaultsで管理している情報の削除、書き換え
  • アプリ内情報の表示

GO でもこれらはありますが、アプリの特徴として

  • 端末の位置によってアプリの挙動が変わる
  • ユーザーの操作以外での画面遷移が多い

などがあり、画面遷移や挙動の確認が大変なため通常のものよりデバッグメニューをリッチにしています。 その分、数が多くどのデバッグメニューが誰向けのものか分かりにくくなるので、セクション(アプリ情報/QA/DEV)で対象を明示したり、アプリ情報、QAセクションに関してはAndroid側ともワーディングや並び順を統一してわかりやすくしています。 この中からいくつかの機能を紹介します。

An image from Notion

位置偽装

タクシーは地域によって呼べるタクシー会社が違ったり、呼ぶことができないエリアがあったりするため、開発中やQA中には案件や確認したい内容によって頻繁に位置情報を変える必要があります。 開発者であればXcodeのSimulate Locationを使えば位置偽装をすることはできますが、緯度経度ベースでしか設定できなかったり、QAチームにXcodeをインストールしてもらうのは大変なため、アプリ内で簡単に位置偽装をできるようにしています。

CLLocationManagerのsetDelegateメソッドをmethod swizzlingすることでGooleMap上の現在地やAccuracyも偽装することができます。

class FakeLocationManager: RuntimeHandler {
    func swizzleCLLocationManagerDelegate() {
        let fromSelector = #selector(setter: CLLocationManager.delegate)
        guard let from = class_getInstanceMethod(CLLocationManager.self, fromSelector),
            let to = class_getInstanceMethod(CLLocationManager.self, #selector(CLLocationManager.swizzleSetDelegate(delegate:))) else {
                fatalError("Swizzle is failed")
        }
        if class_addMethod(CLLocationManager.self,
                           fromSelector,
                           method_getImplementation(to),
                           method_getTypeEncoding(to)) {
            class_replaceMethod(CLLocationManager.self, fromSelector, method_getImplementation(to), method_getTypeEncoding(from))
        } else {
            method_exchangeImplementations(from, to)
        }
    }
}

APIレスポンスの変更

GO は配車依頼をしてから乗車、降車するまでの画面遷移がユーザー操作ではなく、ポーリングしているAPIのレスポンス(タクシーに搭載しているアプリや後部座席に備え付けられているタブレットの操作)によって行われます。 そのためちょっと動作確認するにも各種アプリを準備する必要があり手間がかかるので、APIコールを実際にはせずMockするBuild Configurationを用意しており、それらのレスポンスは デバッグメニュー経由で変更できるようにしています。

Mockビルドの時のみAPIClientをDIで差し替え、API毎にレスポンスを生成して返すように実装しています。 APIが追加されるたびにここの修正が必要になりますが、API開発完了前からアプリ側の開発に着手できるので便利です。

class APIClientMock: APIClient {
    let server: ServerMock

    func response<T>(_ target: T) throws -> (statusCode: Int, data: Data) {
        let encoder = JSONEncoder()

        switch target {
        case let r as API.GetCarRequest:
            if let carRequest = server.findCarRequest(id: r.id) {
                return (200, try encoder.encode(carRequest))
            }
        case let r as API.EnsurePickupRequest:
            if let carRequest = try server.ensureCarRequest(id: r.id) {
                return (200, try encoder.encode(carRequest))
            }
        // APIが追加されたらここにも追加していく
        default: break
        }
        return (200, Data())
    }
}

class ServerMock {
    func findCarRequest(id: CarRequestID) -> CarRequest? {
        var carRequest = CarRequest.createStub()
        // デバッグメニューで設定したものを反映
        return carReque
    }
}

提携施設の可視化

GO には提携しているホテル施設の範囲内に乗車地を設定すると車寄せなどのタクシーが止めやすい場所に位置を調整する機能があります。ただどこがそのホテルの施設の範囲となっているかはぱっと見わからずうまく動かない時に原因の切り分けが大変でした。 そのためどこが施設の範囲なのかをGMSCircleを使って表示するオプションを用意しています。 細かいことですが地図の座標周りの判定などは数字の羅列だけだと何が原因かわかりにくいところなので可視化しておくことで、スムーズに開発できるようにしています。

UITesting

開発中や実装した後にデザインを確認してもらう時にパターンが多いと大変だったりするので、一部のコンポーネントや画面はこのように色々なパターンで確認できるようにしています。 一部はXcode Previewsに置き換えていますが、実機で確認してもらうことが多いので今後も活用していこうと思っています。

An image from Notion

さいごに

デバッグメニューは開発効率向上につなげる以外にも、OSやSwiftの新しい機能が出たときにお試しで入れることができたりする場所にもなるのでこれからもどんどん使い倒していこうと思います!

GO ではSwiftUIをまずはデバッグメニューから導入してからプロダクションコードにも導入していきました。 プロダクションコードにSwiftUIを入れた話についてはRIBs アーキテクチャを採用している既存のアプリに SwiftUI を導入で書かれているのでぜひ読んでみてください!


We're Hiring!

📢
Mobility Technologies ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @mot_techtalk のフォローもよろしくお願いします!