iOSアプリをリニューアルした時のアーキテクチャ

最近、iOSアプリをリニューアルしたのでその時に工夫した内容を書いておこうと思う

前提

  • 仕様書はない
  • iOSアプリでObjective-Cで書かれている
  • Swiftに全てのコードを書き換える。SwiftUIは使わない。
  • 複数アプリが一つのソースコードでプロジェクトで管理されている

最後の部分はあまりないがほとんど同じアプリがコピペで別のリポジトリで管理されるよりはマシな状況である。二つのBuild SettingsでTarget Membershipで分けるのは、読み取りづらいものになっていた。

解決したい問題

  • 典型的なBaseViewController 問題がある
  • FatViewControllerでもある

巨大なViewControllerにベタが記されていて、何が行われているか見通しが悪くなっていた。

解決方法

ViewControllerから処理を剥がすためにフレームワークに分けて、それぞれの役割をはっきりとさせたかった。単純に分けただけでは役割とは異なる箇所にコードを書いてしまうことができるため、フレームワークごとに依存するライブラリを限定して、役割に合っていないフレームワークで書くことを難しくした。さらに、フレームワークごとの依存関係を限定することで、間違った使い方をできないようにした。

3つのフレームワークとアプリに分ける

DomainとInfrastructure、PresenterのフレームワークとそれをまとめるApplicationのアプリに分けることにした。

DomainとInfrastructure、Presenterはそれぞれの役割に集中できるようにフレームワークに分離して、依存する対象をはっきりさせるよにした。たとえば、InfrastructureからPresenterは依存してすることはプロジェクトの設定で絶対にできないようになっている。

Applicationは、複数のアプリの差分(アプリのアイコン等)を持っており、コードの量は最小限になるようにした。

Domain

ドメイン駆動設計で言うところのDomainのこと。ビジネスロジックを可能な限りModelに書くようにした。Respositoryはprotocolになっている。Respositoryを経由してModelを取得するようにしている。 DomainRegistoryは、全てのRepositoryのオブジェクトを持っているか生成するのが役割です。 DomainはRxSwiftのみに依存している。SwiftUIを採用していればRxSwiftに依存せずに別の設計になっていたかもしれない。

import RxSwift

public struct XxxModel {
    let value: String
}

public protocol XxxRepository {
    func load() -> Obserable<Model>
}

public protocol DomainRegistory {
    var xxxRepository: XxxRepository { get }
}

Presenter

Presenterは、主にDomainとUIKitに依存している。
DomainRegistoryが持っているRepositoryを使ってDBや通信の操作するようにしました。これによって、Presenterは本番環境から取得されているか、ステージング環境か、モックかを気にすることなくUIに集中できるようにしました。

Infrastructure

Infrastructureは、主にDomainとDB、API通信クライアントに依存している。
RepositoryImpがRepositoryの実装になっている。通信やキャッシュ、Entityへの変換を実装している。

Application

ApplicationはDomainRegistoryの実装であるDomainRegistoryImpを作成と、環境変数の注入が主な役割である。各アプリ特有の設定や本番とステージング環境の切り替えを行なって、InfrastructureやPresenter現在の実行環境がどうなっているかを気にしなくていいようする役割である。

実際どうだったか

全体の見通しは非常に良く、どこになくを書くべきかが明確なので、失敗しづらい構造になっていました。
今回のアプリはAPI通信で取得したデータをキャッシュして表示する画面がほとんどだったので、ほとんどがアーキテクチャに合わせて構築するだけの部分が多かったため、大きく問題になることはありませんでした。ユーザーの入力が複雑になった場合にRepositoryが複雑になるかはわからなかった。

やらなかったこと

  • 流行りDI コンテナ
    • ここには書かなかったが、RxSwift等を導入したため一同導入するものを減らしたかった
  • SwiftUI
    • iOS12をサポートすると決めていたため

Pull Requestに画像を貼るときはテーブルで書く

UIを変更したら、Pull Requestに変更前と変更後の画像を貼るとレビューしやすいが、非常に縦長になって逆に見辛くなりがち。特にスマートフォンのスクショ。

以下のように書くことで、MarkdownのTableレイアウトで画像を横に並べることができます。

|before|after|
|:--|:--|
|画像1|画像2|

なぜドキュメントを書くのか

なぜ書くのか

記憶は当てにならない

人間は忘れるし、記憶改変が起こるし、病気になったり、トラックにひかれたりする。トラックに引かれなくても、部署変更や転職によって知っている人がいなくなることが往々にして発生する。文章によって残してことによって、忘れたり記憶が改竄されたり伝わることはなくなる。 昔からある機能や成り立ちがわからなくなる。わからなくなったことによって、間違った方向に進んでしまったり、同じ失敗を何度も繰り返す。失敗したことややらないと決めたことを書くことで失敗を繰り返さないようにできる。

ドキュメントを書かないのがアジャイルではない

agilemanifesto.org アジャイルソフトウェア開発宣言では、「包括的なドキュメントよりも動くソフトウェアを」とありますが、ドキュメントを書かないのがアジャイルではない。アジャイルだからドキュメントを書かなくてもいいとは書いていない。ドキュメントもインクリメントに含まれる。

いつ書くのか

知らないことを聞いたとき

初めて聞いたことをドキュメント化していくことで、同じように知らない人がいたときに説明しやすくなる。

知っていることを聞かれたとき

自分が知っていることが、必ずしも他の人が知っているとは限らない。業務上で必要な知識であればリンクを貼っておくだけでもいいので、ドキュメントとして残してあると検索できり経緯がわかる。

貸金業務取扱主任者に合格しました

国家資格でそれなりに難しい。登録しないと名乗れないけど、あまり登録する気はないので名乗りません。

貸金業で開業とかできるようになったけれど、開業には少なくとも5000万円が必要なので、5000万円ください。

ボードゲームを1日2手で1年ほどやっている話

人と何かをしている精神衛生上にいい。

前からボードゲームはしていた

 ボードゲーム歴は、就職してからなので10年くらいある。ゲーム専用機の若干の衰退と、スマフォゲームが流行り始めた頃だった。その頃に出会った友人がボドゲをしていたので始めて、電子ゲームとは違う魅力があって好きになった。会ってその場で行われるリアルな体験が心地よかったのだと思う。  10年くらい、少なくとも月に一度程度は集まってボドゲするようになっていた。

コロナ

 コロナ禍となり、なかなか集まり辛くなった。月に一度程度集まり毎月のようにボードゲームで遊んでいた友達は、大抵はIT系に勤めていたためリモートでボードゲームをすることは自然な流れだった。BoardGameArena でやることになった。BoardGameArenaは前から知っていたはいたが、コンピュータでゲームをするならコンピュータのゲームをしたいと思っていた。どうしても特定のボードゲームをしたいときや、買う前の調査にやるようなものだった。  コロナになり状況は一変した。集まることが憚られるようになった。しばらくのうちは、月に一回音声チャットを繋ぎながらボードゲームを楽しんでいたが、だんだん常にボードゲームをし続けるようになった。常になんらかのボードゲームをして少なくとも朝起きて一手、寝る前に一手進めるようになった。ログインボーナスをもらうように、一手進めている。実際には一日4,5手ほど進めるので1つのゲームが終わるまでに4〜7日程度で終わる。1週間もゲームの内容を覚えてられないと思うかもしれないが、私も覚えていない。常に現在の盤面から次の手を考えているので、ほとんどの場合は覚えている必要はない。  複数人で常に何かをしているというのは楽しい。

ターン制ボードゲームをしよう

1日に2手くらいで朝起きて一手、夜寝る前に一手やろう

Swiftのマクロのorが動かなくてつまづいた

何でかわからないが、 Preprocessor Macros にADHOC=1 やRELEASE=1を追加しても以下のコードが2が出力されてしまった。

#if DEBUG || ADHOC || RELEASE
let i = 1
#else
let i = 2
#end
print(i) // -> 2

DEBUG=1 を入れておくと1になり、or演算子 を続けると無視されます。つまり、分かっていることとしては、or演算子が間違っているということ。 この考えは間違っていた。

解決策

preprocessor macros に定義するという考えが間違っていて、 Active Compilation Conditions に値を定義すべきであった。DEBUGは初めから定義されているため、DEBUGの場合のみ動いているだけだった。

余談

メインのコード内でのマクロによる分岐をやめて、定数として定義して処理を分岐させた方が完結にコードがかけることに気がついた。

enum Configuration {
    case adHoc, debug, release
}

#if ADHOC
let configuration = .adHoc
#elseif DEBUG
let configuration = .debug
#elseif RELEASE
let configuration = .release
#endif

let i = configuration != .release ? 1 : 2
print(i)

実行時間が長くなるもののコード全体としては、マクロによる影響が完結になったと思う。

まとめ

  • Preprocessor Macros に定義してもマクロで使えない
  • Active Compilation Conditions に定義するとマクロで使える
  • マクロの影響範囲を小さくした方が完結な書き方ができてむしろ良かった

クレジットカードは見た目で選んでもいい!

「クレジットカードってWebサイトで買い物する時とか、現金持たなくていいとか、便利な側面はいっぱいあるけど結局どれがいいの?」と思う。大きく分けて、3つある。還元率、優待、見た目だ。

還元率に関しては、このブログを読むよりは、ググった方が良い。一般的に言えることは、1%あれば十分高いということと、よく使っているお店やサイトがあるならそこが推しているものは還元率高め目のことが多い。

優待は、還元率に隠れがちだがこちらの方が割引率が高かったりする。しかし、入ってみるまでよくわからないことが多い。自分の持っているクレジットカードの優待は一度見ておいた方が良い。

持ってて楽しいカードを使った方がいい

還元率は大抵、1%が1.5%になったとか2%になったことで差別化をしている。毎月1万円も使っていない人からしたら、かなり気をつけて100円変わるかわからないかの差しかない。100円だと飲み物が買えないくらいの金額しかならない。その小さな徳によりもクレジットカードを使うたびに楽しくなる自分が選んで最高にカッコいいやカワイイと思って人に自慢したくなる見た目を選んで持っていた方が、楽しく暮らせる。

おすすめのカード

Likeme

www.saisoncard.co.jp 自分の所属している会社なので贔屓して最初に持ってきている。 見た目がいいし、面倒なポイントでなく1%のキャッシュバックであるところも良い。今の弊社の推しカード。

エポスのコラボレーションカード

www.eposcard.co.jp エポスはサブカル系の見た目を多く出している。

三井住友

https://www.smbc.co.jp/kojin/credit/ シンプルな見た目のカード

ラグジュアリカードの金属カード

https://myluxurycard.co.jp/apply/bk_1902_a/ プラスチックとは明らかに異なる質感と高級感 入会費がバカたかい。

あとは自分で探しましょう

https://kakaku.com/card/ranking/ 価格.comあたりが、フィルタリングもできるし見た目で探すのに便利です。