はじめに
SwiftのWebAPIのInterfaceのコードを自動生成する の続きです。
APIレイヤーまでできたので、次はModelっぽいレイヤーまで作ってみます。
この辺りはクライアントアプリ向けですね。
通りが良いのでModelと呼んでますが、今回作ったレイヤーはDataServiceと名づけました。
Model・WebAPI・Entityの自動生成
まず前回と同じようにYAMLを書くのですが、情報としてはAPIのときものもがあれば十分です。
一応以下の様なPrefixを指定するための設定を一つ足せばOKです。
data_service_prefix: QiitaDS
そのYAMLファイルを前回のような感じで実行します。 -t ds
になります。
git clone https://github.com/mokemokechicken/ObjectJsonMapperGenerator.git
cd ObjectJsonMapperGenerator
ruby bin/make_ojm.rb -c data_service.yml -l swift -t ds > data_service.swift
今はこんな感じのコードが生成されます。
https://gist.github.com/mokemokechicken/57396709d2723d39a526
使い方
こんな感じで使います。
import Foundation
private let config = QiitaAPIConfig(baseURL: NSURL(string: "http://qiita.com/api/v2/")!)
private let apiFactory = QiitaAPIFactory(config: config)
private let locator = QiitaDSLocator(factory: apiFactory)
class Sample {
func run() {
let listItem = locator.ListItem
println(listItem.data()) // キャッシュされているデータがあれば来る。今はない。
listItem.addObserver(self) { items, error in // ちょっと変則的?なObserverの登録
items?.map { item in println(item.title) }
println(listItem.data(perPage: 3)!.count) // リクエストパラメータと同じなのでCacheがある
println(listItem.data(perPage: 4)?.count) // これにはない
}
listItem.request(perPage: 3) // APIリクエストを投げる
// listItem.removeObserver(self) // もし removeObserverすれば 参照が開放されて deinit される。
}
deinit {
NSLog("\(self) deinit")
}
}
Sample().run()
CFRunLoopRun()
- ServiceLocator から DataService を取り出します
- data() は事前にrequest()した結果があれば同期的に取得できます。ただしリクエストパラメータが完全に一致する必要があります。
- 後は 基本的にObserverパターンです。 通知のHandlerはクロージャでもMethodでもOKです。
- removeObserverしないとObjectがリークするので、実際に使うときは 必ず実行されるように気をつけましょう。
現在の構造
現在の構造はなんとなくこんな感じです。API系のデータを扱う部分はだいぶカバーできました。
API中心のアプリなら、こんな感じの設計でだいぶ対応できるのではないでしょうか。
Model?DataService?
API層の上はModelと名が付くものが来ることが多いのですが、今回はDataServiceと名づけました。
**Modelのとても大事な役割として「データを守る(整合性)」**ことがありますが、クライアントアプリだと実際は サーバ側にその機能が存在します。クライアント側にもそれを実装することはありますが、気休め程度がほとんどです。
もちろん、クライアント端末のLocalに保存するデータの整合性はクライアントアプリ自体がやらねばならずその場合は間違いなくModelと名が付くものがあったほうが健全に思えます。
自動生成の手前、APIの意味的な集約もなにやら面倒だし、1API-1Object? にすると何かと楽です。
複雑なアプリケーションロジックは別に書いたほうが速いのでそこには触れたくない。
# ちなみに前提として、 自動生成で作ったファイルには一切人間が手を触れない というものがあります。
なので、単に Observerパターン的にアプリにデータを提供したり、アプリからAPIにデータを渡したりするレイヤー として考えて、DataServiceと名づけました。この子賢くないしw(意味的にはAPI生成の情報以上の情報を必要としていないことからもわかる)。
キャッシュ機能とデータ変換機能も持たせるかなぁ、と思って作りましたが、それが良いのかまだ少し検討中です。
人間が記述するコード側からPlugin的に機能を追加する、くらいが適切なのかなぁ、という気がします。
さいごに
最初は単に「JSON Parser作ったら面白いなー」と思ってたらここまで来てました。
コードの自動生成は、ある程度設計ができていれば、「規格化された部品」を作る感覚で非常に効率的だと思っています。自動生成は小回りが効きにくいこともありますが、自動化ならではの力技も可能なので楽しいです。
あ、そういえば、 今回のコードは Release Buildが通りません (--;
swiftのコンパイルの最適化(Swift CompilerのOptimization Level)が有効になっていると失敗するようです。。。 直し方わからないし、放置。。 最適化なしでコンパイルするか、別モジュールでこの部分だけ最適化せずコンパイルして、、、という感じかな。。。いつかコンパイラが賢くなることを期待しましょう。
何かご意見ご要望があれば是非!
Pull Requestも歓迎です!