俺のRemoteNotification 〜 通知方法を考える

#はじめに
ゴールは

>RemoteNotificationをアプリの起動条件関係なく画面表示させたい

です。

AppDelegateで通知を受け取った後の挙動については
選択肢はいくつかあります。

 * Delegate
 * NSNotification
 * KVO

ブロードキャストで通知するControllerは、Observerを大量に使用する場合でなく
今回は表になっている画面にだけ送る仕様にしました。

NSNotificationの使用自体は1:Nに送る場合に便利な機能として知られていて
実装自体が簡易にできてしまうので、Delegateを使うよりNSNotificationを使う方が多い印象があります。

オーバーヘッドが、という話でNSNotificationは使ってなかったのもあり
今回使ってみました。

#実装
ViewController、とNavigationControllerを使う場合で検討します。

##画面構成

![スクリーンショット 2014-12-07 21.42.25.png](https://qiita-image-store.s3.amazonaws.com/0/12362/d1b00632-8020-a5e1-d981-b44e1957dc43.png)

##方針
画面数もほどほどあり、ControllerViewに都度書くのが非効率なので
スーパークラスに実装して派生するようにしました。

構成の所にあるNavigationController以外のSubClassのViewControllerは

>@interface TopViewController : BasicViewController

ControllerViewでは

 * 画面遷移
 * 通知された時の挙動

を個別で書くようにします。

##コード
```objc:Const.h
#define kDidReceiveRemoteNotification @"UIApplicationDidReceiveRemoteNotification"
```

Const.hはAppDelegate.hとBasicViewController.hでimportしています。


```objc:AppDelegate.h
#import <UIKit/UIKit.h>
#import "Const.h"
```

```objc:AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    }
    
    return YES;
}
//(略)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    switch (application.applicationState) {
        case UIApplicationStateActive:
            NSLog(@"StateActive");
            break;
        case UIApplicationStateInactive:
            NSLog(@"StateInactive");
            break;
        case UIApplicationStateBackground:
            NSLog(@"StateBackground");
            break;
        default:
            break;
    }
    [[NSNotificationCenter defaultCenter]
     postNotificationName:kDidReceiveRemoteNotification
     object:self
     userInfo:userInfo];
}
```
```objc:BasicViewController.h
#import <UIKit/UIKit.h>
#import "Const.h"

@interface BasicViewController : UIViewController

- (void)didReceiveRemoteNotification:(NSNotification *)notification;
@end
```


```objc:BasicViewController.m
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(didReceiveRemoteNotification:)
     name:kDidReceiveRemoteNotification
     object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter]
     removeObserver:self
     name:kDidReceiveRemoteNotification
     object:nil];
    [super viewWillDisappear:animated];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)didReceiveRemoteNotification:(NSNotification *)notification
{
    NSLog(@"%s %@",__func__,[notification.userInfo description]);
}

```

#RemoteNotification以外の補足

##画面遷移

```objc
- (IBAction)dismiss:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}
```

UINavigationControllerが親にいる場合

```objc
- (IBAction)dismiss:(id)sender {
    [self.navigationController popViewControllerAnimated:YES];
}
```

##個別にメソッドをoverride

`BasicViewController.m` の`didReceiveRemoteNotification:`をそれぞれのControllerで実装を変えてみます。

```objc
- (void)didReceiveRemoteNotification:(NSNotification *)notification
{
    NSLog(@"%s %@",__func__,[notification.userInfo description]);
}
```
↓

```objc
- (void)didReceiveRemoteNotification:(NSNotification *)notification
{
    //アラートを表示したり、表示データを変更したり

    NSLog(@"%s %@",__func__,[notification.userInfo description]);
    UIAlertController *alertController =
    [UIAlertController alertControllerWithTitle:@"Alert" message:@"iOS8" preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alertController animated:YES completion:^{
            [UIView animateWithDuration:0.8 delay:3
                                options:UIViewAnimationOptionCurveEaseOut
                             animations:^{
                                 alertController.view.alpha  = 0.0;
                             }
                             completion:^(BOOL finished){
                                 [alertController dismissViewControllerAnimated:NO completion:nil];
                             }
             ];
    }];
}
```

#参考URL

iOSハンズオントレーニング observer編 (delegate,notification,KVO)
http://www.slideshare.net/SatosiOkubo/ios-observer-delegatenotificationkvo

[iOS SDK] [memo] NSNotification or KVO or delegate ?
https://hirooka.pro/?p=5124

KVO vs NSNotification vs protocol/delegates?
http://stackoverflow.com/questions/7864838/kvo-vs-nsnotification-vs-protocol-delegates

What is the difference between NSNotificationCenter and the Key Value Observing technique?
http://stackoverflow.com/questions/7040734/what-is-the-difference-between-nsnotificationcenter-and-the-key-value-observing

NSNotification & NSNotification​Center
http://nshipster.com/nsnotification-and-nsnotificationcenter/

#さいごに
普段やらない方法で
普通に、簡単に、通知を送れる内容だったのですが
アプリのライフサイクルやEventの通知など選択肢に対する条件を検討する必要があることがわかりました。

今のやり方だと、NotificationをRemoveしている場合のタイミングの受信を
スルーしてしまう問題点がみえます。

>viewWillDisappear、viewDidDisappearの記述も間違っていました

viewDidDisappearだと次画面のviewDidLoad後、viewDidAppear前に発生していることになります（すみません）。

結局Delegateがいいのか、というと、どうなんでしょう。今は決着がついたのでしょうか。