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

## 構造体の定義

今回は以下のような構造体（ユーザー、ロール）を定義します。

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

    // ParseUserの必須プロパティ
    var username: String?
    var email: String?
    var emailVerified: Bool?
    var password: String?
    var authData: [String: [String: String]?]?

    // 独自のプロパティ
    var customKey: String?
    var scores: ParseRelation<Self>?
}

struct Role<RoleUser: ParseUser>: ParseRole {

    // ParseObjectの必須プロパティ
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var originalData: Data?

    // ParseRoleの必須プロパティ
    var name: String?
}

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

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

## ロールの作成

ロールは子ロールと、ユーザーを持つことができます。以下の例では、 `Administrator` というロールを作成しています。

```swift
// 後述のために保存したロールを保持する変数
var savedRole: Role<User>?
// ACLの定義。現在のユーザーだけが読み書き可能
guard let currentUser = User.current else {
    fatalError("User currently is not signed in")
}
var acl = ParseACL()
acl.setReadAccess(user: currentUser, value: true)
acl.setWriteAccess(user: currentUser, value: true)
// ロールの作成と保存
let adminRole = try Role<User>(name: "Administrator", acl: acl)
adminRole.save { result in
		switch result {
		case .success(let saved): // 保存成功
				// 後で使うように保存したロールを保持
				savedRole = saved
		case .failure(let error): // 保存失敗
				print("Error saving role: \(error)")
		}
}
```

## ロールにユーザーを追加する

既存のロールに対して、新しいユーザーを追加する場合のコードです。

```swift
try savedRole!.users?.add([User.current!]).save { result in
		switch result {
		case .success(let saved): // 保存成功
		case .failure(let error): // 保存失敗
				print("Error saving role: \(error)")
		}
}
```

## 所属するユーザーを取得する

ロールに所属するユーザーを取得する場合は、 `ParseQuery` を使います。

```swift
let query: Query<User>? = try savedRole!.users?.query()
query?.find { result in
		switch result {
		case .success(let relatedUsers): // 取得成功

		case .failure(let error): // 取得失敗
				print("Error querying role: \(error)")
		}
}
```

## ロールからユーザーを削除する

ロールからユーザーを消す場合には `remove` を使います。もちろんACLの設定によっては、削除できない場合もあります。

```swift
try savedRole!.users?.remove([User.current!]).save { result in
		switch result {
		case .success(let saved):
				print("The role removed successfully: \(saved)")
				print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")

		case .failure(let error):
				print("Error saving role: \(error)")
		}
}
```

## 既存のロールに新しいロールを追加する

ロールは入れ子の構造にできます。以下の例では、 `Administrator` ロールに `moderatorRole` ロールを追加しています。

```swift
try savedRole!.roles?.add([moderatorRole!]).save { result in
		switch result {
		case .success(let saved): // 保存成功
		case .failure(let error): // 保存失敗
				print("Error saving role: \(error)")
		}
}
```

## 子ロールを取得する

子ロールを取得する際には `queryRoles` を使います。

```swift
try savedRole!.queryRoles().find { result in
		switch result {
		case .success(let relatedRoles): // 取得成功
		case .failure(let error): // 取得失敗
				print("Error querying role: \(error)")
		}
}
```

## 子ロールを削除する

子ロールを削除する場合には `remove` を使います。

```swift
try savedRole!.roles?.remove([savedRoleModerator!]).save { result in
		switch result {
		case .success(let saved):
				print("The role removed successfully: \(saved)")
				print("Check the \"roles\" field in your \"Role\" class in Parse Dashboard.")

		case .failure(let error):
				print("Error saving role: \(error)")
		}
}
```

## まとめ

Parse Serverのロールオブジェクトは、NCMBと変わらず利用できます。ユーザーを入れる形もそうですし、ロールを入れ子にできるのもNCMBと変わりません。

ACLの仕組みも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)