[iOS] AppDelegateを綺麗に保つ4つのテクニック

  • 389
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

AppDelegateはアプリ全体のライフタイムイベントを管理するためのクラスですが、その性質上、様々な処理が書かれやすいです。

しかし、あらゆる処理が書かれ肥大化していくと、見通しが悪くなってメンテナンスがしづらくなったり、チームで開発してる場合はコンフリクトが起こるなど開発速度に支障をきたすようになってしまう場合があります。

そこで、この記事では、そんな膨れがちなAppDelegateを綺麗な状態に戻すための方法をいくつか紹介します。

1. AppDelegateの責務外の処理は他クラスに移す

AppDelegateの主な責務はライフタイムイベントの管理です。具体的には「起動」「停止」「バックグラウンド状態の切り替わり」などなどUIApplicationDelegateで定義されているような処理です。

にもかかわらず、例えば全Controllerから触れる値を定義したいなどの理由で、責務と全く関係ないような処理をAppDelegateに書いてしまうと、どんどん肥大化していって役割が不明確になっていってしまいます。

そのような場合、各処理を役割にあった別のクラスに切り出してみると、見通しがよくなります。

改善例

before( ログイン状態のフラグをAppDelegateが管理 )

例えば、以下はログイン状態のフラグをAppDelegateに書いてしまっている例です。

Appdelegate.swifft
class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    // アプリのライフタイムサイクルと無関係であるログイン処理が
    // AppDelegateに書かれてしまてっている
    var isLogin:Bool = false
    ...
}
HogeViewController
class HogeViewController: UIViewController {
    override func viewDidLoad() {
        ...
        // こうすれば全Controllerからログイン状態を取得できるが....
        let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let isLogin = appDelegate.isLogin
    }
}

after( ログイン関係の処理はAuthServiceへ )

例えば以下のようにAuthServiceといったようにログイン状態を管理することを責務としたクラスを作って、そこに処理を切り出すとAppDelegateはスッキリし、さらにControllerの処理も読みやすくなります。

AppDelegate.swifft
class AppDelegate: UIResponder, UIApplicationDelegate {
    // AppDelegateからはログイン関係の処理は消して専用クラスに移動させる
}
AuthService.swifft
// ログイン状態を管理するための専用のクラスを作る
class AuthService: UIResponder, UIApplicationDelegate {
    // AppDelegateにあったログイン関係の処理はこちらに全て移動
    var isLogin:Bool = false;
    class var sharedManager : AuthService { ... }
    func login(){ ... }
    func logout(){ ... }
}
HogeViewController
// コントローラーも、こちらのほうがシンプルになる
class HogeViewController: UIViewController {
    override func viewDidLoad() {
        ...
        let isLogin  = AuthService.sharedService.isLogin
        ...
    }
}

2. 細かな初期化処理をAppDelegateから各クラスに移す

アプリ起動後にAppDelegatedidFinishLaunchingWithOptionsが呼ばれます。そのため、ここに全体的な機能の初期化処理を行うことが多いです。

しかし、ここで複雑な初期化処理があるとAppDelegateの見通しが悪くなってしまうケースがあります。初期化自体、自分のクラスで行えるのであればAppDelegateではなく、各クラスの初期化時に行ったほうが良いです。

改善例

before( 細かな設定がAppDelegateで行われている )

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        ...
        // リモートロガーの設定
        MyRemoteLogger.setBufferingInterval(5)
        MyRemoteLogger.setRemoteHost("http://example.com/logger")
        MyRemoteLogger.setAppName("MyApp")
        MyRemoteLogger.setTimeZone(xxx)
        ...
    }
}

after( 細かな初期化処理は、各クラス側へ )

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // AppDelegateの中では以下を呼ぶだけ
        MyRemoteLogger.configure()
    }
}
MyRemoteLogger.swift
class MyRemoteLogger:NSObject {
    class func configure(){
        // 複雑な初期化は各クラスで行う
        MyRemoteLogger.setBufferingInterval(5)
        MyRemoteLogger.setRemoteHost("http://example.com/logger")
        MyRemoteLogger.setAppName("MyApp")
        MyRemoteLogger.setTimeZone(xxx)
    }
}

また、初期化のタイミングが遅延(必要になったときに呼ぶ)できる設計に変更できる場合は、遅延させるようにするとAppDelegateから完全に初期化処理を消すこともできます。

初期化してるクラスが外部ライブラリの場合は?

外部ライブラリを使ってる場合は、上のように自分でメソッドを生やしたりすることはできません。
そのような場合は、そのライブラリを利用するためのラッパークラスの作成を検討してみても良いかもしれません。

例えばGoogleAnalyticsであれば直接GAIインスタンスを触るのではなくAppNnameAnalyticsのようなクラスを1つ作って、そこでアプリ独自の設定などを書く等です。

といっても、そのラッパークラスの中身が、初期化数行だけ等だと、逆に複雑になるだけの可能性もあるので規模や状況に応じて、検討してみてください。

3. NSNotificationCenterの利用を検討する

AppDelegateの主要なdelegateメソッドは以下のようにNSNotificationCenterでも監視することができます。

NSNotificationCenterでバックグラウンドへの移動を監視
 NSNotificationCenter.defaultCenter().addObserver( self,
    selector: "methodName",
    name: UIApplicationDidEnterBackgroundNotification,
    object: nil
)

改善例

例えば、アプリが何らかのサービスのHTTPなAPIを利用していて結果を端末にcacheしているような場面があったとします。そしてアプリがバックグラウンドにいった際にcacheするような仕様だったとします。

そんなケースだと以下のようにAppDelegateから、該当クラスに処理を移すことができます。

before( AppDelegateの中にcacheクリア処理がある )

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    func applicationDidEnterBackground(application: UIApplication) {
        // AppDelegateの中でcacheのクリア処理を実行している
        APIClient.sharedClient.clearCache()
    }
    ...
}

after( cacheクリアもAPIClientクラスで完結出来る )

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func applicationDidEnterBackground(application: UIApplication) {
        // AppDelegateから該当の処理は消える
    }
}
APIClient.swift
class APIClient: NSObject {
    ...
    override init() {
        super.init()
        // 各クラスがNSNotificationCenterでバックグラウンド入りを監視
        NSNotificationCenter.defaultCenter().addObserver( self,
            selector: "clearCache",
                name: UIApplicationDidEnterBackgroundNotification,
              object: nil
        )
    }
    ...
}

4. AppDelegateをサブクラスに分割する

どうしてもAppDelegateで実行しなければいけない処理が多様にある場合もあります。例えば広告などの各種SDKが指定されていて、多くの会社のSDKを導入している場合などですね。

そのような処理が多く、見通しが悪くなっている場合は、種類ごとにAppDelegateをまとめてサブクラスにすると全体の見通しをよくすることができるかもしれません。

改善例

before( 様々な種類の処理が混ざっている )

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // 広告会社AのSDK処理
        AdsADK_A.sharedSDK.onApplicationDidFinishLaunching()

        // 広告会社BのSDK処理
        AdsADK_B.registerConvertionID("xxx", mediaKey:"yyy")

        // 広告会社CのSDK処理
        AdsADK_C.register(appKey:"aaa")

        // 広告以外の処理
    }
    ...
    // 他のメソッドでも同様にSDKの決まりの処理が多くて、他の処理が見づらくなっている
}

after( 広告SDK周りの処理のみサブクラスへ委譲 )

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

        // 広告の処理は別クラスAppDelegateForAdsADKを作り、そちらへ委譲
        AppDelegateForAdsADK.sharedDelegate.application(application, 
            didFinishLaunchingWithOptions: launchOption
        )

        // 広告以外の処理
    }
    // 他のdelegateメソッドでも同様に変更
}

まとめ

自分がAppDelegaetを整理する中で、意識したところや試した方針をまとめてみました!並べてみると、どれも「クラスの責務を適切に分解する」という基本的な原則に従うための方針になっていますね。

もしAppDelegateが肥大化していて整理したい!という状況の方がいれば参考にしてみてください。