NCMBObjectはサブクラス化して使おう！



NCMBの`NCMBSubclassing` について触れられている情報が少ないと感じましたので、簡単な内容ですが投稿します。無駄な説明は省いて、重要なポイントだけシンプルに示します。

## 動作確認環境

- [NCMB 2.2.5](https://github.com/NIFTYCloud-mbaas/ncmb_ios/releases)
- OS X EI Capitan 10.11.2 (15C50)
- Xcode Version 7.2.1 (7C1002)
- iPhone 6s Plus (iOS 9.2.1)

対象のデータストア

![ncmb-data-store.png](https://qiita-image-store.s3.amazonaws.com/0/5243/757a272a-e38f-f6f3-ed1e-bdc36e9cb114.png)


## NCMBObjectをサブクラス化しない場合

```swift
func addLocation() {

    // ...サブクラス化した場合と同じなので省略

    let location = NCMBObject(className: "Location")
    location.setObject(geoPoint, forKey: "geoPoint")
    location.setObject("新宿駅", forKey: "name")

    // ...サブクラス化した場合と同じなので省略
}
```

## NCMBObjectをサブクラス化した場合

```swift
/// 位置情報をデータストアに追加します
func addLocation() {
    let latitude = Double(35.690921)
    let longitude = Double(139.700258)
    let geoPoint = NCMBGeoPoint(latitude: latitude, longitude: longitude)

    let location = Location.object() as! Location
    location.geoPoint = geoPoint
    location.name = "新宿駅"
    location.saveInBackgroundWithBlock { error in
        if let e = error {
            print("保存失敗: \(e)")
            return
        }
        print("保存成功")
    }
}

/// ストアデータの位置情報を検索します
func searchLocation() {

    let latitude = Double(35.690921)
    let longitude = Double(139.700258)
    let geoPoint = NCMBGeoPoint(latitude: latitude, longitude: longitude)

    let geoQuery = Location.query()
    // 現在のSDKでは、検索対象フィールド（プロパティ）を文字列で指定しなければいけないのが残念
    geoQuery.whereKey("geoPoint", nearGeoPoint: geoPoint, withinKilometers: Double(2.0))
    geoQuery.limit = 20

    do {
        let points = try geoQuery.findObjects() as NSArray
        for point in points as! [Location] {
            print(point.name)
        }
    } catch {
        print("失敗: \(error)")
    }
}
```

このようにNCMBObjectをサブクラス化した場合は、

- 若干ソースコードの可読性が向上する
- プロパティの型チェックが行われる

という最低でも2つのメリットが得られると思います。

## サブクラス化したLocationクラスの内容

一目瞭然ですが、NCMBのSDKはSwiftで記述されていないので、プロパティ名とオブジェクトの出し入れをする際のキー名が一致していることが重要です。 また、NCMBSubclassingプロトコルに準拠する必要があります。 `@objc` 属性が必要であることにも注意してください。

```swift
import NCMB

@objc(Location) // この宣言をしないと、サブクラスの登録時にクラッシュします
public class Location: NCMBObject, NCMBSubclassing {
    var name: String! {
        get {
            return objectForKey("name") as! String
        }
        set {
            setObject(newValue, forKey: "name")
        }
    }

    var geoPoint: NCMBGeoPoint {
        get {
            return objectForKey("geoPoint") as! NCMBGeoPoint
        }
        set {
            setObject(newValue, forKey: "geoPoint")
        }
    }

    // MARK: - NCMBSubclassing Protocol

    /// mobile backend上のクラス名を返す
    /// - returns: サブクラスのデータストア上でのクラス名
    public static func ncmbClassName() -> String! {
        return "Location" // クラス名と同じ名前であること
    }
}
```

## サブクラス化したクラスを使用するための前準備

```swift
func setupNCMB() {
    // NCMBSubclassingに準拠しているクラスのセットアップ
    Location.registerSubclass()

    // NCMBのセットアップ
    NCMB.setApplicationKey(applicationKey, clientKey: clientKey)
}
```

このように、NCMBのセットアップの直前で、サブクラスを登録する必要があります。


## まとめ

NCMBに限らず、Parseやその他のmBaaSのSDKでも同様に、基底クラスをサブクラス化する仕組みが提供されているので、できるだけ利用した方が良いと思います。今後SDKがSwift対応すれば、クラスの記述もよりSwiftらしい記述ができるようになるはずです。（ParseSDKはすでにそうなっています）

余計なコードが入っていますが、本記事で説明したコードもありますので、参照されたい方はどうぞ。
https://github.com/naokits/my-programming-marathon/tree/master/NCMB_iOSAppDemo


## 関連情報
- [SDKガイド (iOS) : サブクラス | ニフティクラウド mobile backend](http://mb.cloud.nifty.com/doc/current/sdkguide/ios/subclass.html)
- [NCMBSubclassing Protocol Reference](http://mb.cloud.nifty.com/assets/sdk_doc/ios/doc/html/Protocols/NCMBSubclassing.html)
- [Using Swift with Cocoa and Objective-C (Swift 2.1): Interacting with Objective-C APIs](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_36%22Swift%20Type%20Compatibility%22)
- [【Swift×ニフティクラウドmobile backend】Swift版ドキュメントを作ってみました！＜データストア編①＞ - Qiita](http://qiita.com/natsumo/items/c00cf7a48e0f8cd8d236)