parse.comからNIFTY Cloud mobile backendへ移行した話（Part1）

個人で開発しているアプリ、keyaki2のバックエンドをparse.comからNIFTY Cloudへ移行した話です。
keyaki2は欅坂46のブログリーダーです。メンバーごとに新着ブログの通知をプッシュで送ってくれます。

ついでにCocoaPodsの導入、Gitの導入と、開発と本番で叩くAPIを変えるように実装したいと思っています。
***クソプロジェクトを保守しやすいマシなものにしよう（メインはmBaaSの切り替え）***
っていう大きなテーマを掲げていきたいと思います。  

以降、NIFTY Cloud Mobile BackendをNCMBと称します。
##現在の状態
【Parse版 keyaki2】
　CocoaPods未導入。手作業の温かみでライブラリを追加していました。

##具体的な移行手順
いきなりParse版からNCMB版に切り替えてしまうと、いろいろ大変なことが起きそうなので
移行期間を作ってゆっくり移行していきたいと思います。

###1. Parse版にNCMBのSDKをインストール  
こちらも手作業の温かみが良さそうなのでGitHubからSDKを落としてきて入れようとしたのですが、
なぜかうまく行かなかったので、結局CocoaPodsをこちらにも導入することにしました。

ターミナル上で`.xcodeproj`があるディレクトリに移動して
`$ pod init`
生成されたPodfileに

```ruby:Podfile
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

target 'keyaki2' do
  # Comment this line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for keyaki2
  pod 'NCMB', :git => 'https://github.com/NIFTYCloud-mbaas/ncmb_ios.git'
end
```
`pod NCMB...`を追記して保存します。
`$ pod install` を実行してCocoaPodsのプロジェクトを作成します。

生成された`.xcworkspace`をXcodeで開いてビルドが通ればとりあえずOKです。

###2. DeviceTokenの設定を行う
必要とする場所に `#import <NCMB/NCMB.h>` を記述してください。
僕の場合は`PrefixHeader.h`にライブラリ関係を入れているので、そこに記述しました。（楽ちん

まずはDeviceTokenをParseにも、NCMBにも登録するために`AppDelegate.m`にいろいろ書いていきます。
アプリのキーや設定関係をコードに直接書くのがイヤなので、僕はConst.hというファイルに定数を置いてます。
Parseの設定ができているのであれば、一行追加するだけでOKです。

```objc:AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Parseの初期化
    [Parse setApplicationId:K2ParseAppId clientKey:K2ParseClientKey];

    //NCMBの設定を行う
    [NCMB setApplicationKey:K2NiftyAppId clientKey:K2NiftyClientKey];

    //通知の設定(デバイストークンの取得などを行う)
    UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
                                                    UIUserNotificationTypeBadge |
                                                    UIUserNotificationTypeSound);
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
                                                                             categories:nil];
    [application registerUserNotificationSettings:settings];
    [application registerForRemoteNotifications];

    //略
}
```

```objc:Const.h
//Parse関連
static NSString *const K2ParseAppId = @"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static NSString *const K2ParseClientKey = @"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
//Parse関連
static NSString *const K2NiftyAppId = @"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
static NSString *const K2NiftyClientKey = @"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";

```

次に、DeviceTokenが返ってきた時にNiftyにも送るようにコードを書いていきます。
同じく`AppDelegate.m`に記述していきます。基本的には追記していくだけです。

```objc:AppDelegate.m
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    //Parse版で元々記述されていた処理
    PFInstallation *currentInstallation = [PFInstallation currentInstallation];
    [currentInstallation setDeviceTokenFromData:deviceToken];
    [currentInstallation saveInBackground];
    
    //以下を追記
    //端末情報を扱うNCMBInstallationのインスタンスを作成
    NCMBInstallation *installation = [NCMBInstallation currentInstallation];
    
    //Device Tokenを設定
    [installation setDeviceTokenFromData:deviceToken];
    
    //端末情報をデータストアに登録
    [installation saveInBackgroundWithBlock:^(NSError *error) {
        if(!error){
            //端末情報の登録が成功した場合の処理
        } else {
            //端末情報の登録が失敗した場合の処理
            if (error.code == 409001){
                //失敗した原因がdeviceTokenの重複だった場合
                [self updateExistInstallation:installation];
            } else if (error.code == 404001) {
                //deviceTokenの重複以外のエラーが返ってきた場合
                [self reRegistInstallation:installation];
            }
        }
    }];
}

//deviceTokenの重複で端末情報の登録に失敗した場合に上書き処理を行う
- (void)updateExistInstallation:(NCMBInstallation*)currentInstallation{
    NCMBQuery *installationQuery = [NCMBInstallation query];
    [installationQuery whereKey:@"deviceToken" equalTo:currentInstallation.deviceToken];
    
    NSError *searchErr = nil;
    NCMBInstallation *searchDevice = [installationQuery getFirstObject:&searchErr];
    
    if (!searchErr){
        //上書き保存する
        currentInstallation.objectId = searchDevice.objectId;
        [currentInstallation saveInBackgroundWithBlock:^(NSError *error) {
            if (!error){
                //端末情報更新に成功したときの処理
            } else {
                //端末情報更新に失敗したときの処理
            }
        }];
    } else {
        //端末情報の検索に失敗した場合の処理
    }
}

- (void) reRegistInstallation:(NCMBInstallation *) installation {
    installation.objectId = nil;
    [installation saveInBackgroundWithBlock:^(NSError *error){
        if (!error) {
            NSLog(@"NCMB installation : successfully re-regist.");
        } else {
            NSLog(@"NCMB installation : failed to re-regist. %@",error);
        }
    }];
}

```

これでDeviceTokenがParseとNiftyの両方に向くようになりました。
基本的な移行版の作成はこれだけでいいのですが、keyaki2ではメンバーごとにブログの通知を送るため、
ユーザはそれぞれチャンネルに登録を行っています。今回はその設定もNiftyに移行したいため、
こちらのコードも書いていきます。

###3.Channelsの設定をNiftyへ移行する
通知チャンネルの移行は、「Parseで登録しているチャンネルリストをNiftyにも送る」という方法で
いきたいと思います。
以下に移行するためのメソッドを示します。僕はどこからでも呼べるようにUtilsクラス内に入れました。

```objc:Utils.h
+(void)syncNotificationSettings;
```

```objc:Utils.m
+(void)syncNotificationSettings{
    NSArray *subscribedChannels = [PFInstallation currentInstallation].channels;
    NCMBInstallation *installation = [NCMBInstallation currentInstallation];
    [installation setObject:subscribedChannels forKey:@"channels"];
    //端末情報をデータストアに保存
    [installation saveInBackgroundWithBlock:^(NSError *error) {
        NSLog(@"installationIdInBlock:%@", installation.objectId);
        if(!error){
            //端末情報の登録が成功した場合の処理
            NSLog(@"Sync successful!");
        } else {
            //端末情報の登録が失敗した場合の処理
            NSLog(@"Sync failed...");
        }
    }];
}
```

とりあえずこれ呼んでおけば同期できるっぽいのでいいのかな…と思ってたりします。
パフォーマンス的にどうかとは思いますが（未検証）、とりあえずAppDelegate内でそういう処理を書いちゃえば
とりあえず移行はできるだろうということで…。
再び`AppDelegate.m`に戻って

```objc:AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //どこか適当な位置に(return直前とかいいかもしれない)
    [Utils syncNotificationSettings];
    
    return;
}
```
って置いておけばコールドスタート時にはとりあえず設定は同期できるはずです。
あとはユーザがchannelの設定を変更した時にもこのメソッドを呼べば完璧ですね。

一旦この状態でアプリをビルド、リリースしてユーザには移行期間版を使ってもらうことにします。

次回は新アプリをCocoaPodsで作成して、コードを移行したり整理したりしたいと思います。