iOS開発(Swift)のおすすめ構成〜ライブラリ編〜

botman_blue iOS

はじめに

パッケージマネージャ編の続きです。
最近よく使ってるライブラリの紹介です。基本的にこいつらは標準装備でいいんじゃないかなと思ってます。

プロジェクトのフォルダ構成は下記でやってます。

directory

R.swift (5.2.2)

mac-cain13/R.swift リソース管理がいい感じになる標準で使うべきやつ!!(似たようなので SwiftGen/SwiftGen ってのもあるみたいです)

  1. Pods でインストール
  2. Run Script 追可
  3. Run Script に記載
    if which $PODS_ROOT/R.swift/rswift >/dev/null; then
    "$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/$PRODUCT_NAME/Resource/R.generated.swift"
    else
    echo "warning: R.swift not installed, download from https://github.com/mac-cain13/R.swift"
  4. Input Files に $TEMP_DIR/rswift-lastrun を追加
  5. Outout Files に $SRCROOT/$PRODUCT_NAME/Resource/R.generated.swift を追加
  6. ビルドして R,generated.swift を生成
  7. R,generated.swift をプロジェクトに追加

こんな感じ

rswift

エラーになる場合は下記色々試すといいかも

  • cmd + shift + K でクリーン
  • ライブラリ⁩/Developer⁩/Xcode⁩/DerivedData 内のファイルを削除(念の為ゴミ箱からも削除)
  • Run script の位置を上の方に変える(Dependencies の下とか)

これが

こうなる

swiftlint (0.39.2)

realm/SwiftLint リントツール!これも標準で入れていいと思う!!(自動修正もできるみたいだけど覚える意味もこめて私は使ってないです)

  1. Pods でインストール
  2. プロジェクトフォルダに .swiftlint.yml
  3. Run Script 追可
  4. Run Scriptに記載
    if which ${PODS_ROOT}/SwiftLint/swiftlint >/dev/null; then
    ${PODS_ROOT}/SwiftLint/swiftlint
    else
    echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
    fi

.swiftlint.yml はこんな感じ(〜Tests 系を除外するかは要相談)

ルール詳細は公式をどうぞ

カスタムルール

カスタムのルールも作成でき形式は下記のような感じ。

match_kinds は下記。

  • argument
  • attribute.builtin
  • attribute.id
  • buildconfig.id
  • buildconfig.keyword
  • comment
  • comment.mark
  • comment.url
  • doccomment
  • doccomment.field
  • identifier
  • keyword
  • number
  • objectliteral
  • parameter
  • placeholder
  • string
  • string_interpolation_anchor
  • typeidentifier

例えば下記のようにやると ~ViewModel.swift ファイルで import UIKit をするとエラーをはく。(import と UIKit の間にスペース入れたりで逃げれるので厳密にやろうと思うともうちょっと工夫がいる)

一部ルールの有効無効

コード上でたまにここだけは無効にしたいとかあると思いますがそういうのもちゃんと用意されています。

最強のやつ

指定のルールを無効化する

注意下記のようにすると以降全て無効化される

上のように this とかを指定しない場合は enable で挟んだ方がいい。(じゃないとせっかくのルールが。。。)

LicensePlist (2.15.1)

mono0926/LicensePlis ライセンス表示をめっちゃ簡単にしてくれるやつ!これも標準で入れていいと思う!!!

  1. Pods でインストール
  2. Settings.bundle を追加
  3. Root.plist 変更
  4. Run Script 追加

Settings.bundle を追加する。

setting_bundle

Settings.bundle の Root.plist を下記のようにする。

ついでにアプリのバージョンも表示するのに下記の Run Script を追加するといいと思う。

これで設定アプリに下記のように表示してくれる。

設定 ライセンス一覧 ライセンス
setting license_list license_detail

iOS 13 では設定アプリのバグがあり --single-page オプションていうのも追加されました。(参考:iOS13の設定アプリにつまずいた)

DIKit (0.5.0)

ishkawa/DIKit DI 用にぜひ!!(Swinject/Swinject っていうのもあるみたいです)

  1. Carthage でインストール
  2. DIKit から zip をダウンロード
  3. zip 解凍
  4. ターミナルで解凍したディレクトリに移動
  5. ターミナルで make install コマンド実行
  6. Xcode で Run Script 追加
    if which dikitgen >/dev/null; then
    dikitgen ${SRCROOT}/$PRODUCT_NAME > ${SRCROOT}/$PRODUCT_NAME/Resource/AppResolver.generated.swift
    else
    echo "warning: dikitgen not installed, download from https://github.com/ishkawa/DIKit"
    fi
  7. ビルドして AppResolver.generated.swift を生成
  8. プロジェクトに AppResolver.generated.swift を追加

とりあえず適当に動かしてみる。

下記のように AppResolver.swift を追加する。

適当に ViewController.swift とかに下記のように追加してみる

これでビルドすると AppResolver.generated.swift は下記のように生成される

あとは下記の DI プロトコルを使っていい感じに DI する!!

SwiftyBeaver (1.9.1)

SwiftyBeaver/SwiftyBeaver ログ出力におすすめ!!Mac アプリを使えば実機ログも確認できる!!!

  1. Carthage でインストール
  2. AppDelegate とかで初期化
  3. ログをはく

AppDelegate.swift のトップで下記のように初期化する。本番ではログ出力したくないので Debug のみにして出力先もコンソールのみに設定しています。

下記のようにするとクラウドとファイルにも出力できるらしい。クラウドの方は Mac アプリがありそこから見れるようです。(アカウント作成必要。フリープランもあり。)

参考

ログレベルは下記5つ

試しに出力してみるとこんな感じ

log1

ログのフォーマット設定もできる(使ったことはないです)。試しに下記のようにしてみる。

こんな感じ

log2

フォーマットは下記を設定できるらしい。

Variable Description
$L Level, for example "VERBOSE"
$M Message, for example the foo in log.debug("foo")
$J JSON-encoded logging object (can not be combined with other format variables!)
$N Name of file without suffix
$n Name of file with suffix
$F Function
$l Line (lower-case l)
$D Datetime, followed by standard Swift datetime syntax
$d Datetime format end
$T Thread
$C Color start, is just supported by certain destinations and is ignored if unsupported
$c Color end
$U Uptime in the format HH:MM:SS
$X Optional context value of any type (see below)

Realm (5.0.3)

realm/realm-cocoa DB ならこれが楽!!

  1. Carthage でインストール
  2. Object 作成
  3. DB 処理

Realm Browser という Mac アプリがあり DB の中身も確認できる!と思ったけどなんかファイル開くときにキーを求められる。。。そして何も設定してないのでこのキーがわからない( Realm.Configuration.defaultConfiguration.encryptionKey こいつか?と思ったけど nil だった。。。)

realm_browser

調べると Realm Studio というのがあるらしいのでこっちを使おう!!
デフォルトだと Documents ディレクトリに default.realm が生成されるのでこれを取得する。

場所がわからなければ下記のようにログで確認できます。

初期化

保存先を変えたりするのは下記のように設定する

読み込み専用ならプロジェクト内に relam ファイルを追加して下記のようにできる

Configuration のイニシャライザはこんな感じになっている(なんか前 deleteRealmIfMigrationNeeded に true が設定されててマイグレーションが変な感じになってたことがある。。。)

削除

DB ごと削除したい場合は下記のように .realm ファイル以外にも色々削除しないといけない。

モデル

Realm で使えるデータ型は下記。

Type Non-optional Optional
Bool @objc dynamic var value = false let value = RealmOptional()
Int @objc dynamic var value = 0 let value = RealmOptional()
Float @objc dynamic var value: Float = 0.0 let value = RealmOptional()
Double @objc dynamic var value: Double = 0.0 let value = RealmOptional()
String @objc dynamic var value = "" @objc dynamic var value: String? = nil
Data @objc dynamic var value = Data() @objc dynamic var value: Data? = nil
Date @objc dynamic var value = Date() @objc dynamic var value: Date? = nil
Object n/a: must be optional @objc dynamic var value: Class?
List let value = List() n/a: must be non-optional
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") n/a: must be non-optional

参考:realm - Property cheatsheet

こんな感じで定義する。

let は使えないみたい。(詳しくなんて書いてるかわからないけど dynamic var のみみたいなんが書いてると思う。。。)

Realm model properties must have the @objc dynamic var attribute to become accessors for the underlying database data. Note that if the class is declared as @objcMembers (Swift 4 or later), the individual properties can just be declared as dynamic var.

主キーやインデックスも設定できる

Ignoring properties っていうのもあるらしいけどどういうとき使うんだろ??DB には保存しないらしいです。

多対1もしくは1対1の関係を表したい場合はプロパティに持たせるだけでOK

保存してみるとこんな感じ。Person のデータを更新すると Dog の owner も反映されます。

realm_relation

多対多の場合は List を使います。操作はほとんど Array と同じ。

双方向にデータをたどりたい場合 Person に dogs を定義していると Person -> Dog はたどれますが Dog -> Person はたどれません。仮に Dog に owner を定義しても owner 更新時に Person の Dogs を更新してはくれません。そういった場合は下記のように LinkingObjects を利用します。(LinkingObject を追加しても DB 上は Dog テーブルに項目が増えたりはしません。)

DB操作

データ追加

上記実行して Realm Studio で確認するとこんな感じ

realm_studio

データ更新

こういう書き方もできる

下記のように update: .modified を指定するとすでに同じ主キーのデータがある場合は更新、ない場合は追加になる。(主キーを設定していない場合は使えない。)

下記のようにすると同じ主キーのデータがあれば age のみ更新され、ない場合は name がデフォルト値でデータが追加される。

データ削除

データ検索

Predicate いろいろ

  • 数値 と Date は ==, <=, <, >=, >, !=, BETWEEN を使える
  • objects.filter("name == %@", name) とかできる
  • String と Data は ==, !=, BEGINSWITH, CONTAINS, ENDSWITH を使える
  • LIKE も対応 ex. value LIKE '?bc* とか ? は1文字 * は 0以上の文字列を表す
  • IN も対応 ex. name IN {'Hoge', 'Piyo', 'Foo'}
  • AND, OR, NOT も使える
  • name == nil で nil チェックもできる
  • List と Results では @count, @min, @max, @sum, @avg も使える ex. objects.filter("dogs.@count > 5")

細かいのは realm - Filtering を確認。

検索結果は自動で更新される

ソートもできる

ドキュメントが充実してるのでその他詳細は realm ドキュメント をどうぞ。

Quick/Nimble (3.0.0/8.1.1)

Quick/Quick と Quick/Nimble ユニットテスト用におすすめ!!柔軟な表現力!!!エラーメッセージがとにかくわかりやすい!!!!

  1. Carthage でインストール(テスト用なので Cartfile.private でいい)
  2. テストを書く!

試しに Hoge 構造体のテストを書いてみます。

こんな感じです。describe がテスト対象で context が条件で it が期待する動作です。it は describe のことで it の部分は hogehogeWithIsHoge method returns hogehoge when isHoge is true. のような感じに文章になるはず。(英文として正しいか知らんけど。。。)

書き方は README に色々のってますが個人的によく使うのだけ書いときます。

一時的に無効化する場合は context とかの前に x をつける

指定のやつだけ実行したい場合は context とかの前に f をつける

すごい!日本語ドキュメントもあった!!

APIKit

ishkawa/APIKit 軽量な HTTP クライアント! Alamofire だと過剰かな?と思ったらこっち!!!通信周りは全部自前でやってもいいけど URL エンコードとかわりとめんどくさいのでライブラリに頼った方が無難です。

  1. Carthage でインストール
  2. Request プロトコルに準拠したリクエスト作成
  3. Session.send で API をコール

Request は下記

実際に Codable を利用して お天気Webサービス(Livedoor Weather Web Service / LWWS) で天気を取得してみます。

実装は簡単!処理結果が Result なのもわかりやすくていい!!
もうちょっとどうにか抽象化したかったけど思いつかなかった。。。(関係ないけど Codable でネストした値とれるの知らなかった!すげぇ!! nestedUnkeyedContainer, nestedContainer)

APIKit の処理を閉じ込めたくて APIKit.Request に依存しない Request を作ってアダプターみたいなので APIKit.Request に変換してとか色々模索したけどここまでくると通信処理自作した方がいい気がしてきた。。。

Firebase/Analytics

アナリティクスでよくいれることになるやつ。設定めんどくさかったので今回は割愛します。。。

おわりに

このあたりのライブラリは UIKit に依存してるわけでもないので今後もしばらくは使っていくと思います。
使ったことないけどいいなと思うのが yonaskolb/XcodeGen。YAML でプロジェクトデータを記述して *.xcodeproj ファイルを git 管理から外せるらしい。(コンフリクトが激減する!!)どっかで使ってみたい。

https://amzn.to/3OiClcG

コメント

  1. […] ライブラリ編の続きです。 おすすめ構成を実践してみました。今回アーキテクチャに関しては特に触れないですがプレゼンテーションロジックをどっかに置きたいと思い PresentationModel というよくわからないものを置いています。 […]

  2. […] iOS みたいに Android のおすすめライブラリを紹介したいと思います。しかし、iOSと違って Android は実践したことはないです。。。(たぶんこれいいんじゃないかなぁ?って感じで書いてます) […]

タイトルとURLをコピーしました