ニフクラ mobile backendは3月末で終了します。
ニフクラ mobile backendからの移行先として、お勧めしているのがParse Serverです。設計思想が近く、変更するコード量が少なく済むのではないかと思います。
Parse ServerではiOS向けにSDKを提供していますが、Swift SDKについてはあまりドキュメントが充実していません。そこで、各機能の使い方を解説します。今回はデータストアに対するクエリー操作について紹介します。
Swift SDKとObjective-C SDKのどちらを使うべきか
Parse ServerにはSwift SDKとObjective-C SDKが用意されています。機能的にいうと、Objective-C SDKの方が多いようです。しかし、公式メッセージとしてはSwift SDKを使っていくのを奨励しています。
Parse Server Swift SDKのインストール
Swift SDKのインストールは、CocoaPodsやCarthageなどが使えます。しかし、一番簡単なのはXcodeのPackage Dependenciesを使う方法でしょう。
XcodeのFileメニューより、Add Package Dependenciesを選択して、出てきたダイアログで以下のURLを指定します。
https://github.com/parse-community/Parse-Swift.git
そして、利用するファイルでSDKをインポートします。
import ParseSwift
初期化
SwiftUIの例です。初期化は (アプリ名)App.swift
にて行います。そして、初期化は ParseSwift.initialize
にて行います。指定するアプリケーションID、クライアントキー、サーバーURLはそれぞれParse Serverを立ち上げる際に指定したものを使います。
マスターキーも指定できるようですが、アプリ側では使わない方が良いかと思います。
import SwiftUI
import ParseSwift
@main
struct ParseDemoApp: App {
init() {
ParseSwift.initialize(applicationId: "YOUR_APP_ID", clientKey: "YOUR_CLIENT_KEY", serverURL: URL(string: "https://example.com/parse")!)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
構造体の作成
データストアに対するクエリー操作を行う際には、 ParseObject
を継承した構造体を作成します。
struct GameScore: ParseObject {
// 必須のプロパティ
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
// 独自のプロパティ
var points: Int?
var timeStamp: Date? = Date()
var oldScore: Int?
var isHighest: Bool?
}
クエリーの基本形
クエリーは query
メソッドを使います。引数は検索条件です。
let afterDate = Date().addingTimeInterval(-300)
var query = GameScore.query("points" > 50,
"createdAt" > afterDate)
.order([.descending("points")])
クエリーの実行
クエリーの実行は find
メソッドを使います。非同期での実行は以下の通りです。注意点は、検索結果がない場合 .failure
になることです。
query.limit(2)
.order([.descending("points")])
.find(callbackQueue: .main) { results in
switch results {
case .success(let scores):
scores.forEach { score in
guard let createdAt = score.createdAt else { fatalError() }
print("スコア: \(score)")
}
case .failure(let error):
if error.equalsTo(.objectNotFound) {
assertionFailure("検索結果はありません")
} else {
assertionFailure("実行エラー: \(error)")
}
}
}
同期での実行は以下の通りです。
let results = try query.find()
results.forEach { score in
guard let createdAt = score.createdAt else { fatalError() }
print("Found score: \(score)")
}
最初の1件だけを取得
最初の1件だけを取得する場合は、 first
メソッドを使います。非同期での実行は以下の通りです。この場合はParseObjectが返ってきます。
query.first { results in
switch results {
case .success(let score):
guard score.objectId != nil,
let createdAt = score.createdAt else { fatalError() }
print("スコア: \(score)")
case .failure(let error):
if error.containedIn([.objectNotFound, .invalidQuery]) {
assertionFailure("検索結果がない、またはクエリーが不正です")
} else {
assertionFailure("実行エラー: \(error)")
}
}
}
結果行数も取得する
結果行数も取得する場合は、 withCount
メソッドを使います。非同期での実行は以下の通りです。
query.withCount { results in
switch results {
case .success(let (score, count)):
print("スコア: \(score) トータル行数: \(count)")
case .failure(let error):
if error.containedIn([.objectNotFound, .invalidQuery]) {
assertionFailure("検索結果がない、またはクエリーが不正です")
} else {
assertionFailure("実行エラー: \(error)")
}
}
}
相対的な時間でのクエリー
相対的な時間でのクエリーは relative
メソッドを使います。非同期での実行は以下の通りです。
let queryRelative = GameScore.query(relative("createdAt" < "in 10 minutes"))
queryRelative.find { results in
switch results {
case .success(let scores):
print("検索結果: \(scores)")
case .failure(let error):
print("実行エラー: \(error)")
}
}
結果のフィールドを絞り込む
select
メソッドを使うと、結果のフィールドを絞り込めます。非同期での実行は以下の通りです。
let querySelect = query.select("points")
querySelect.first { results in
switch results {
case .success(let score):
guard score.objectId != nil,
let createdAt = score.createdAt else { fatalError() }
print("検索結果: \(score)")
case .failure(let error):
if let parseError = error.equalsTo(.objectNotFound) {
assertionFailure("オブジェクトがありません: \(parseError)")
} else {
assertionFailure("実行エラー: \(error)")
}
}
}
逆に取り除く場合には、 exclude
メソッドを使います。非同期での実行は以下の通りです。
let queryExclude = query.exclude("points")
queryExclude.first { results in
switch results {
case .success(let score):
guard score.objectId != nil,
let createdAt = score.createdAt else { fatalError() }
print("検索結果: \(score)")
case .failure(let error):
if let parseError = error.containedIn(.objectNotFound, .invalidQuery) {
assertionFailure("オブジェクトがありません: \(parseError)")
} else {
assertionFailure("実行エラー: \(error)")
}
}
}
まとめ
以上、Parse Server Swift SDKのデータストアクエリー操作について解説しました。NCMBの検索方式とは若干異なる印象です。注意点は、検索結果がない場合にエラーになることでしょう。
とはいえParse Serverには結果フィールドの絞り込みなど、便利な機能もあります。NCMBと設計思想も似ていますので、他のmBaaSと比べても移行時の修正量はそこまで多くないと思われます。載せ替え先として検討に挙げてください。