AppDelegate
はアプリ全体のライフタイムイベントを管理するためのクラスですが、その性質上、様々な処理が書かれやすいです。
しかし、あらゆる処理が書かれ肥大化していくと、見通しが悪くなってメンテナンスがしづらくなったり、チームで開発してる場合はコンフリクトが起こるなど開発速度に支障をきたすようになってしまう場合があります。
そこで、この記事では、そんな膨れがちなAppDelegate
を綺麗な状態に戻すための方法をいくつか紹介します。
1. AppDelegateの責務外の処理は他クラスに移す
AppDelegate
の主な責務はライフタイムイベントの管理です。具体的には「起動」「停止」「バックグラウンド状態の切り替わり」などなどUIApplicationDelegateで定義されているような処理です。
にもかかわらず、例えば全Controllerから触れる値を定義したいなどの理由で、責務と全く関係ないような処理をAppDelegate
に書いてしまうと、どんどん肥大化していって役割が不明確になっていってしまいます。
そのような場合、各処理を役割にあった別のクラスに切り出してみると、見通しがよくなります。
改善例
before( ログイン状態のフラグをAppDelegateが管理 )
例えば、以下はログイン状態のフラグをAppDelegate
に書いてしまっている例です。
class AppDelegate: UIResponder, UIApplicationDelegate {
...
// アプリのライフタイムサイクルと無関係であるログイン処理が
// AppDelegateに書かれてしまっている
var isLogin:Bool = false
...
}
class HogeViewController: UIViewController {
override func viewDidLoad() {
...
// こうすれば全Controllerからログイン状態を取得できるが....
let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let isLogin = appDelegate.isLogin
}
}
after( ログイン関係の処理はAuthServiceへ )
例えば以下のようにAuthService
といったようにログイン状態を管理することを責務としたクラスを作って、そこに処理を切り出すとAppDelegate
はスッキリし、さらにController
の処理も読みやすくなります。
class AppDelegate: UIResponder, UIApplicationDelegate {
// AppDelegateからはログイン関係の処理は消して専用クラスに移動させる
}
// ログイン状態を管理するための専用のクラスを作る
class AuthService: UIResponder, UIApplicationDelegate {
// AppDelegateにあったログイン関係の処理はこちらに全て移動
var isLogin:Bool = false;
class var sharedManager : AuthService { ... }
func login(){ ... }
func logout(){ ... }
}
// コントローラーも、こちらのほうがシンプルになる
class HogeViewController: UIViewController {
override func viewDidLoad() {
...
let isLogin = AuthService.sharedService.isLogin
...
}
}
2. 細かな初期化処理をAppDelegateから各クラスに移す
アプリ起動後にAppDelegate
のdidFinishLaunchingWithOptions
が呼ばれます。そのため、ここに全体的な機能の初期化処理を行うことが多いです。
しかし、ここで複雑な初期化処理があるとAppDelegate
の見通しが悪くなってしまうケースがあります。初期化自体、自分のクラスで行えるのであればAppDelegate
ではなく、各クラスの初期化時に行ったほうが良いです。
改善例
before( 細かな設定がAppDelegateで行われている )
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( 細かな初期化処理は、各クラス側へ )
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// AppDelegateの中では以下を呼ぶだけ
MyRemoteLogger.configure()
}
}
class MyRemoteLogger:NSObject {
class func configure(){
// 複雑な初期化は各クラスで行う
MyRemoteLogger.setBufferingInterval(5)
MyRemoteLogger.setRemoteHost("http://example.com/logger")
MyRemoteLogger.setAppName("MyApp")
MyRemoteLogger.setTimeZone(xxx)
}
}
また、初期化のタイミングが遅延(必要になったときに呼ぶ)できる設計に変更できる場合は、遅延させるようにするとAppDelegate
から完全に初期化処理を消すこともできます。
初期化してるクラスが外部ライブラリの場合は?
外部ライブラリを使ってる場合は、上のように自分でメソッドを生やしたりすることはできません。
そのような場合は、そのライブラリを利用するためのラッパークラスの作成を検討してみても良いかもしれません。
例えばGoogleAnalytics
であれば直接GAI
インスタンスを触るのではなくAppNameAnalytics
のようなクラスを1つ作って、そこでアプリ独自の設定などを書く等です。
といっても、そのラッパークラスの中身が、初期化数行だけ等だと、逆に複雑になるだけの可能性もあるので規模や状況に応じて、検討してみてください。
3. NSNotificationCenterの利用を検討する
AppDelegate
の主要なdelegate
メソッドは以下のようにNSNotificationCenter
でも監視することができます。
NSNotificationCenter.defaultCenter().addObserver( self,
selector: "methodName",
name: UIApplicationDidEnterBackgroundNotification,
object: nil
)
改善例
例えば、アプリが何らかのサービスのHTTPなAPIを利用していて結果を端末にcache
しているような場面があったとします。そしてアプリがバックグラウンドにいった際にcache
するような仕様だったとします。
そんなケースだと以下のようにAppDelegate
から、該当クラスに処理を移すことができます。
before( AppDelegateの中にcacheクリア処理がある )
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func applicationDidEnterBackground(application: UIApplication) {
// AppDelegateの中でcacheのクリア処理を実行している
APIClient.sharedClient.clearCache()
}
...
}
after( cacheクリアもAPIClientクラスで完結出来る )
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidEnterBackground(application: UIApplication) {
// AppDelegateから該当の処理は消える
}
}
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
が肥大化していて整理したい!という状況の方がいれば参考にしてみてください。