Parse Server Swift SDKの使い方（位置情報オブジェクトの使い方）

[ニフクラ mobile backendは3月末で終了します](https://mbaas.nifcloud.com/info/2023/12/post-171.html)。

ニフクラ mobile backendからの移行先として、お勧めしているのが[Parse Server](https://parseplatform.org/)です。設計思想が近く、変更するコード量が少なく済むのではないかと思います。

Parse ServerではiOS向けにSDKを提供していますが、Swift SDKについてはあまりドキュメントが充実していません。そこで、各機能の使い方を解説します。今回は位置情報オブジェクトの使い方を紹介します。

## Swift SDKとObjective-C SDKのどちらを使うべきか

Parse ServerにはSwift SDKと[Objective-C SDK](https://docs.parseplatform.org/ios/guide/)が用意されています。機能的にいうと、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をインポートします。

```swift
import ParseSwift
```

## 初期化

SwiftUIの例です。初期化は `(アプリ名)App.swift` にて行います。そして、初期化は `ParseSwift.initialize` にて行います。指定するアプリケーションID、クライアントキー、サーバーURLはそれぞれParse Serverを立ち上げる際に指定したものを使います。

マスターキーも指定できるようですが、アプリ側では使わない方が良いかと思います。

```swift
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()
        }
    }
}
```

## 構造体の定義

位置情報オブジェクトは、データストアの1フィールドとして用います。ParseGeoPointが位置情報オブジェクトを保存するフィールド定義になります。

```swift
struct GameScore: ParseObject {
    //: ParseObjectの必須プロパティ
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var location: ParseGeoPoint?
    var originalData: Data?

    //: 独自のプロパティ
    var points: Int?
}
```

## 位置情報オブジェクトのセット

`ParseGeoPoint` を該当フィールドに対して適用します。1つ目の引数が緯度、2つ目の引数が経度です。

```swift
var score = GameScore(points: 10)
do {
    try score.location = ParseGeoPoint(latitude: 40.0, longitude: -30.0)
}
```

## 位置情報オブジェクトの保存

保存は通常のデータストアと同じく `save` メソッドを使います。

```swift
score.save { result in
    switch result {
    case .success(let savedScore):
        assert(savedScore.objectId != nil)
        assert(savedScore.createdAt != nil)
        assert(savedScore.updatedAt != nil)
        assert(savedScore.points == 10)
        assert(savedScore.location != nil)
        guard let location = savedScore.location else {
            print("エラー")
            return
        }
        print(location)
    case .failure(let error):
        assertionFailure("保存エラー: \(error)")
    }
}
```

## 位置情報を使った検索

以下は位置情報を使った検索例です。 `near` は指定した位置から近い順に検索します。

```swift
let pointToFind = try ParseGeoPoint(latitude: 40.0, longitude: -30.0)
constraints.append(near(key: "location", geoPoint: pointToFind))
query = GameScore.query(constraints)
query.find { results in
		switch results {
		case .success(let scores):
				assert(scores.count >= 1)
				scores.forEach { (score) in
					// 何か処理
				}
		case .failure(let error):
				assertionFailure("クエリーエラー: \(error)")
		}
}
```

並び替えを別途指定することもできます。

```swift
var querySorted = query
querySorted.order([.descending("points")])
querySorted.find { results in
    switch results {
    case .success(let scores):
        assert(scores.count >= 1)
        scores.forEach { (score) in
					// 何か処理
        }

    case .failure(let error):
        assertionFailure("クエリーエラー: \(error)")
    }
}
```

## 他の条件を含める

位置情報以外の条件を含めることもできます。

```swift
constraints.append("points" > 9)
var query2 = GameScore.query(constraints)
query2.find { results in
    switch results {
    case .success(let scores):
        assert(scores.count >= 1)
        scores.forEach { (score) in
					// 何か処理
        }
    case .failure(let error):
        assertionFailure("クエリーエラー: \(error)")
    }
}
```

## 位置情報がないものを検索

位置情報が設定されていないものを検索する場合には、 `doesNotExist` を使います。

```swift
var query3 = GameScore.query("points" > 50, doesNotExist(key: "location"))
query3.find { results in
    switch results {
    case .success(let scores):
        scores.forEach { (score) in
					// 何か処理
        }
    case .failure(let error):
        assertionFailure("Error querying: \(error)")
    }
}
```

## 位置情報が空のものを検索

フィールドはあっても、値が設定されていないものを検索する場合には、 `isNull` を使います。

```swift
var anotherQuery3 = GameScore.query("points" > 50, isNull(key: "location"))
anotherQuery3.find { results in
    switch results {
    case .success(let scores):
        scores.forEach { (score) in
					// 何か処理
        }
    case .failure(let error):
        assertionFailure("クエリーエラーError querying: \(error)")
    }
}
```

## ポリゴン検索

Parse ServerではNCMBにはない、3カ所以上の位置情報を用いたポリゴン検索ができます。

```swift
let points: [ParseGeoPoint] = [
		try .init(latitude: 35.0, longitude: -28.0),
		try .init(latitude: 45.0, longitude: -28.0),
		try .init(latitude: 39.0, longitude: -35.0)
]
let query9 = GameScore.query(withinPolygon(key: "location", points: points))
query9.find { results in
		switch results {
		case .success(let scores):
				scores.forEach { (score) in
						// 何か処理
				}
		case .failure(let error):
				assertionFailure("Error querying: \(error)")
		}
}
```

## まとめ

Parse Serverの位置情報オブジェクトは、NCMBと変わらず利用できます。また、Parseにはポリゴンという複数の位置情報を利用したオブジェクトも利用できます。地図を利用したアプリなどで活躍することでしょう。

データの管理方法などはParse ServerとNCMBで似ています。他のmBaaSと比べると、全体の修正量はそこまで多くないと思われます。載せ替え先として検討に挙げてください。

[parse\-community/Parse\-Swift: The Swift SDK for Parse Platform \(iOS, macOS, watchOS, tvOS, Linux, Android, Windows\)](https://github.com/parse-community/Parse-Swift)