AppDelegateに良く組み込む処理

UIApplicationDelegateプロトコルの実装から良く呼び出す処理のメモです。

# アプリ起動時
`– application:didFinishLaunchingWithOptions:`から呼び出す各種初期化、設定処理。

## Exceptionをコンソールに出力するよう設定

例外発生時、開発中はトレースをコンソールに書き出し。これはmain.mでやっても良さそうです。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|OFF|OFF|ON|

```objc:AppDelegate.h
void exceptionHandler(NSException *exception) {
    NSLog(@"stackSymbols:%@", [exception callStackSymbols]);
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#if DEBUG
    NSSetUncaughtExceptionHandler(&exceptionHandler);
#endif
}
```

参考：[How do you print out a stack trace to the console/log in Cocoa?](http://stackoverflow.com/questions/220159/how-do-you-print-out-a-stack-trace-to-the-console-log-in-cocoa "How do you print out a stack trace to the console/log in Cocoa?")

## 通信ログ

AFNetworkingでの通信をコンソールに出力するため、[AFNetworkActivityLogger](https://github.com/AFNetworking/AFNetworkActivityLogger, "AFNetworkActivityLogger")をON/OFF。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|OFF|OFF|ON|

```objc
#if DEBUG
    [[AFNetworkActivityLogger sharedLogger] startLogging];
    [[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelDebug];
#endif
```

## TestFlight初期化

テスト配信に[TestFlight](https://www.testflightapp.com/ "TestFlight")を使っているのでその初期化です。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ON|OFF|OFF|

```objc
[TestFlight takeOff:<TEST_FLIGHT_ID>];
```

## NewRelic初期化

[NewRelic](http://newrelic.com/ "NewRelic")で各種測定をする時はその初期化。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ON|ON|ON|

```objc
[NewRelicAgent startWithApplicationToken:<NEWRELIC_ID>];
```

## クラッシュレポート系SDK初期化

[Crittercism](crittercism.com "Crittercism")や[Crashlytics](https://try.crashlytics.com/ "Crashlytics")の初期化をしています。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ON|ON|OFF|

```objc
[Crittercism enableWithAppID:<CRITTERCISM_ID>];
```

## リモートエラーログ

例外は発生していないが、エラーである状態をNSAssertが検知した時、配信版アプリでは（送信して良い時は）端末からログを送信、開発中はログを送信せずにその箇所でブレークする。

```objc
// デバッグ時はここでブレーク
// 配信版はTTSAssertHandlerのメソッドが呼び出される。（TTSAssertHandlerは後述）
NSAssert(object, @"objectがありません"); 
```

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ログ送信|ログ送信|NSAssertの箇所でブレーク|

### リモートログ送信先
+ Crashlytics(CLS_LOG)
+ Crittercism(logHandledException)
+ TestFlightリモートログ（TFLog）
+ Google AnalyticsにExceptionとして書き込む(100文字以内) (GAIDictionaryBuilderクラスのcreateExceptionWithDescription:withFatal:)
+ [NSLogger](https://github.com/fpillet/NSLogger "NSLogger")で特定のMacに書き込む。
+ [AirBrake SDK](https://github.com/airbrake/airbrake-ios "airbrake-ios")でExceptionとしてAirbrakeに投げる

### NSAssertHandlerの子クラス

```objc:TTSAssertHandler.h
#import <Foundation/Foundation.h>
@interface TTSAssertionHandler : NSAssertionHandler
@end
```

```objc:TTSAssertHandler.m
#import "TTSAssertionHandler.h"
@implementation TTSAssertionHandler

- (void)handleFailureInMethod:(SEL)selector
                       object:(id)object
                         file:(NSString *)fileName
                   lineNumber:(NSInteger)line
                  description:(NSString *)format, ...
{
    va_list args;
    va_start(args, format);
    NSString *header = [NSString stringWithFormat:@"[%@ %@ %i]", NSStringFromClass([object class]), NSStringFromSelector(selector), line];
    NSString *reason = (format) ? [[NSString alloc] initWithFormat:format arguments:args] : nil;
    NSString *log = [NSString stringWithFormat:@"%@ %@ %@", header, reason, object]];
    va_end(args);
    // ここでどこかにlog送信
}

- (void)handleFailureInFunction:(NSString *)functionName
                           file:(NSString *)fileName
                     lineNumber:(NSInteger)line
                    description:(NSString *)format, ...
{
    va_list args;
    va_start(args, format);
    NSString *header = [NSString stringWithFormat:@"[%@:%i]", functionName, line];
    NSString *reason = (format) ? [[NSString alloc] initWithFormat:format arguments:args] : nil;
    NSString *log = [NSString stringWithFormat:@"%@ %@", header, reason]];
    va_end(args);
    // ここでどこかにlog送信
}

@end
```

参考：[NSAssertion​Handler and NSAssert](http://nshipster.com/nsassertionhandler/ "NSAssertion​Handler")

## スタブ

APIとの通信が発生するアプリで[OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs "OHHTTPStubs")を使ってモックデータを返す必要がある場合のみ、設定しています。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|OFF|OFF|必要時のみON|

```objc
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
    // requestがスタブを有効にしたいURLの時にYESを返す
    return YES;
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
    NSString *fileName = @"<ファイル名>";
    NSString *fixture = OHPathForFileInBundle(fileName, nil);
    return [OHHTTPStubsResponse responseWithFileAtPath:fixture
                                            statusCode:200
                                               headers:@{<ヘッダー>}];
}];
```

## アクセス解析用SDK

Google Analytics SDK等、アクセス解析用のSDKを初期化。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ON（開発用）|ON（本番用）|ON（開発用）|

```objc
[GAI sharedInstance].trackUncaughtExceptions = NO;
[GAI sharedInstance].dispatchInterval = 10;
[[[GAI sharedInstance] logger] setLogLevel:kGAILogLevelNone];
[[GAI sharedInstance] trackerWithTrackingId:<GA_TRACKING_ID>];
```

## Lumberjack

ロギングに[Lumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted, "Lumberjack")を使っている時のみ初期化。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|OFF|OFF|ON|

```
//https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted
[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor orangeColor] backgroundColor:nil forFlag:LOG_FLAG_INFO];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor redColor] backgroundColor:nil forFlag:LOG_FLAG_ERROR];
```

## キーチェーンからデータ復元

暗号化しておきたい情報はキーチェーンに保存しているので、そのデータを復元。[SSKeychain](https://github.com/soffes/sskeychain "SSKeychain")を使用。

## キーチェーンのクリア

開発中に初期状態のキーチェーンを使いたいケースが多々あるので、クリアする機能を持たせています。これも[SSKeychain](https://github.com/soffes/sskeychain "SSKeychain")。

## DB作成もしくはマイグレーション

DBを使うときはDB作成、バージョンアップ時はそのマイグレーション。

## DBのクリア

デバッグ中に初期状態のDBを使いたいケースが多々あるので、デバッグ中のみDBクリアする機能を持たせています。

# 通知

通知を使用する場合のみ。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ON|ON|ON|

* DeviceToken取得

```objc
static inline BOOL IsEmpty(id thing) {
    return thing == nil
    || thing == [NSNull null]
    || ([thing respondsToSelector:@selector(length)]
        && [(NSData *)thing length] == 0)
    || ([thing respondsToSelector:@selector(count)]
        && [(NSArray *)thing count] == 0);
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *deviceTokenString = [[[deviceToken description]
                               stringByReplacingOccurrencesOfString:@"<"withString:@""]
                              stringByReplacingOccurrencesOfString:@">" withString:@""];
    if (!IsEmpty(deviceTokenString)) {
        // DeviceToken保存
    }
}
```

* `[UIApplication sharedApplication].applicationIconBadgeNumber`を0に戻す。

# Facebookログイン

Facebookログインを組み込む場合のみ。

|AdHoc配信版アプリ|AppStore配信版アプリ|開発版アプリ|
|:-----------:|:------------:|:------------:|
|ON|ON|ON|

# 
### Facebookログイン後のコールバック対応

参考：[Handling the Response from the Facebook app]
(https://developers.facebook.com/docs/facebook-login/ios/v2.0#button-response "Handling the Response from the Facebook app")

```objc
 - (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
        BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
        return wasHandled;
    }
```

### セッションクローズ

```objc
- (void)applicationWillTerminate:(UIApplication *)application
{
    [FBSession.activeSession close];
}
```


### セッション更新

```objc
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // セッション更新
    [FBAppCall handleDidBecomeActive];
    // Facebookログインせずにホームボタンでアプリに戻った時はログインキャンセル
    if (FBSession.activeSession.state == FBSessionStateClosedLoginFailed && <ログイン画面か判定>) {
        // ログインキャンセル処理
    }
}
```

参考：[Facebookログイン](https://developers.facebook.com/docs/tutorials/ios-sdk-games/authenticate/jp "Facebookログイン")
参考：[FBAppCall仕様](https://developers.facebook.com/docs/reference/ios/current/class/FBAppCall "FBAppCall")