NCMBのSwift SDKでネットワーク処理を同期的に行う

NCMBのデータアクセスはネットワーク処理も伴うものなので、基本的に非同期になります。取得したデータによって処理分けしたりする場合、複数のネットワーク処理を順番に行わなければいけません。

コールバック方式を多用するとネストが深くなってしまうので、Swift SDKではDispatchSemaphoreを使った同期処理をサポートしています。

## 処理の内容

今回は匿名認証（ID、パスワードの不要な認証）を使い、さらにそのセッションチェックを行うコードを紹介します。つまり次のような処理になります。

1. 現在ログインしているか確認
    - ログインしていない場合は匿名認証実行
2. セッションの有効性チェックとしてデータストアにアクセス
    - エラーの場合には認証情報を削除
3. ログインしていない場合は匿名認証実行

といった流れになります。

## 実際のコード

上記処理部分のコードは次のようになります。このコードだけ見ると、処理が非同期ではないように見えないでしょうか。

```swift
// 初期化
let applicationKey = KeyManager().getValue(key: "applicationKey") as! String
let clientKey = KeyManager().getValue(key: "clientKey") as! String
NCMB.initialize(applicationKey: applicationKey, clientKey: clientKey)
NCMBUser.enableAutomaticUser() // 匿名認証の有効化
// ログインチェック&匿名認証実行
checkAuth()
// セッションの有効性チェック
if checkSession() == false {
    // セッションが無効だったら再度ログインチェック&匿名認証実行
    checkAuth()
}
```

## 認証チェック処理

認証チェック用の `checkAuth` 関数の内容です。ローカルにある認証データは `NCMBUser.currentUser` で復元されます。このデータがあれば、処理は終了です。この時点では非同期処理は発生していません。

認証データがない場合は、匿名認証 `NCMBUser.automaticCurrentUser`を実行します。この関数では DispatchSemaphore が使われており、ネットワーク処理が処理待ちの状態になります。

```swift
func checkAuth() -> Void {
    // 認証データがあれば処理は終了
    if NCMBUser.currentUser != nil {
        return;
    }
    _ = NCMBUser.automaticCurrentUser()
}
```

該当部分のコードです。 `DispatchSemaphore` を使って非同期処理を同期的に行っています。

```swift
public class func automaticCurrentUser() -> NCMBResult<NCMBUser> {
    var result : NCMBResult<NCMBUser> = NCMBResult.failure(NCMBApiErrorCode.genericError)
    let semaphore = DispatchSemaphore(value: 0)
    automaticCurrentUserInBackground(callback: {(res: NCMBResult<NCMBUser>) -> Void in
        result = res
        semaphore.signal()
    })
    _ = semaphore.wait(timeout: DispatchTime.distantFuture)
    return result
}
```

## セッションの有効性チェック

セッションの有効性チェックも基本は同じです。今回は関数の返値が Void ではなく、真偽値としています。この場合は返値となる変数を var で定義しておいて、非同期処理の結果に応じて上書きします。

```swift   
func checkSession() -> Bool {
    var query : NCMBQuery<NCMBObject> = NCMBQuery.getQuery(className: "Todo")
    query.limit = 1 // レスポンス件数を最小限にする
    // アクセス
    let results = query.find()
    // 結果の判定
    switch results {
    case .success(_): break
    case .failure(_):
        // 強制ログアウト処理
        _ = NCMBUser.logOut()
        return false
    }
    return true
}
```

なお、上記のログアウト処理 `NCMBUser.logOut` は必ず失敗します。なぜならセッションが無効であるためにログアウト用のAPI実行も失敗するためです。そこで `ncmb_swift/NCMB/NCMBUser.swift` を開いて、 `logOutInBackground` 関数を修正します。

```swift
/// ログアウトを非同期処理にて行います。
///
/// - Parameter callback: レスポンス取得後に実行されるコールバックです。
public class func logOutInBackground(callback: @escaping NCMBHandler<Void>) -> Void {
    NCMBLogoutService().logOut(callback: {(result: NCMBResult<NCMBResponse>) -> Void in
        // 以下の2行を上に持ってくる
        _currentUser = nil // この行
        deleteFile()       // この行
        switch result {
            case .success(_):
                // 元々はここにあります
                callback(NCMBResult<Void>.success(()))
                break
            case let .failure(error):
                callback(NCMBResult<Void>.failure(error))
                break
        }
    })
}
```

このようにログアウトAPIの結果に関わらずローカルデータは消しておくことで、強制ログアウト処理が可能になります。

## まとめ

DispatchSemaphoreを使えばNCMBのネットワーク処理が同期的に書けるようになるので、コードが見やすくなります。Swift SDKで用意されている同期処理用のメソッドを活用してください。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)