Refactoring Massive App Delegate
실제 production app 을 개발하다 보면 AppDelegate
클래스가 쉽게 비대해지고 파일수가 어마어마하게 늘어나는 경우가 비일비재하다. 이를 refactoring 하는 방법
Mediator Design Pattern
refactoring-massive-app-delegate 에 보면 다양한 디자인 패턴으로 appDelegate를 refactoring 하는 법이 나와있다 그중 mediator design pattern 으로 실제 활용한 예
AppLifecycle Mediator
- 먼저 AppLifeCycle mediator 를 정의한다.
- 아래 소스는
appDelegate.m
이 objective-c 로 구현되어 있기에NSObject
를 상속받고objc
propertyObserver를 붙였다.
import Foundation
// MARK: - AppLifecycleListener
protocol AppLifecycleListener {
func onAppWillEnterForeground()
func onAppDidEnterBackground()
func onAppDidFinishLaunching()
}
extension AppLifecycleListener {
func onAppWillEnterForeground() {}
func onAppDidEnterBackground() {}
}
// MARK: - Mediator
@objc
class AppLifecycleMediator: NSObject {
private let listeners: [AppLifecycleListener]
init(listeners: [AppLifecycleListener]) {
self.listeners = listeners
super.init()
subscribe()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func subscribe() {
NotificationCenter.default.addObserver(self, selector: #selector(onAppWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onAppDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onAppDidFinishLaunching), name: UIApplication.didFinishLaunchingNotification, object: nil)
}
@objc private func onAppWillEnterForeground() {
listeners.forEach { $0.onAppWillEnterForeground() }
}
@objc private func onAppDidEnterBackground() {
listeners.forEach { $0.onAppDidEnterBackground() }
}
@objc private func onAppDidFinishLaunching() {
listeners.forEach { $0.onAppDidFinishLaunching() }
}
}
AppListener class 구현
- 이제
AppLifecycleListener
를 상속하여 클래스를 구현한다. - appDelegate 에서 구현된 각각의 서비스 구현들을 여기에 Extract 하여 구현
- 예를 들면 기존에 AppDelegate에서 구현되어있던 location permission 관련 feature 들은 아래와 같이
AppLocationListener
구현한다.
import Foundation
import CoreLocation
import LoggingKit
class AppLocationListener: NSObject, AppLifecycleListener {
let locationManager = CLLocationManager()
fileprivate func permissionState(_ status: CLAuthorizationStatus) {
LogService.shared.debug("LocationPermission: \(status.rawValue)", logCategory: \.debug)
switch status {
case .authorizedAlways, .authorizedWhenInUse:
locationManager.startUpdatingLocation()
case .restricted, .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .denied:
stopLocation()
@unknown default:
print("GPS: Default")
}
}
func stopLocation() {
locationManager.stopUpdatingHeading()
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
}
func onAppDidFinishLaunching() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
let status: CLAuthorizationStatus
if #available(iOS 14.0, *) {
status = locationManager.authorizationStatus
} else {
status = CLLocationManager.authorizationStatus()
}
permissionState(status)
}
}
extension AppLocationListener: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.permissionState(status)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations[0]
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(newLocation) { (playmarks, error) in
if let _ = error {
self.stopLocation()
return
}
if let city = playmarks?.first?.locality {
cityDefault.saveLocationLng("\(newLocation.coordinate.longitude)")
cityDefault.saveLocationLat("\(newLocation.coordinate.latitude)")
cityDefault.saveLocationCity(city)
}
}
stopLocation()
}
}
- 그리고
AppLifecycleMediator
에 listener 에 추가
extension AppLifecycleMediator {
@objc
static func makeDefaultMediator() -> AppLifecycleMediator {
let locationListener = AppLocationListener()
let debugListener = AppDebugListener()
return AppLifecycleMediator(listeners: [debugListener, locationListener])
}
}
- 3rdParty service 들, debug feature 등 listener 로 추가하여 refactoring 해주면 된다.
add mediator to AppDelegate
- 이제 mediator class 를 AppDelegate 에 등록한다.
- 다음 코드 한줄만 appDelegate 에 추가하면 된다.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let mediator = AppLifecycleMediator.makeDefaultMediator() // 여기에 추가
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
return true
}
}
- objective-c 구현에 붙인다면 조금 애매한데
willFinishLaunchingWithOptions
메소드에 다음과 같이 추가하였다.
@interface AppDelegate ()<CLLocationManagerDelegate, UNUserNotificationCenterDelegate, FIRMessagingDelegate, AppsFlyerLibDelegate>
{
AppLifecycleMediator *_mediator; // 여기에 선언하고
}
...
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_mediator = [AppLifecycleMediator makeDefaultMediator]; // 구현추가.
return YES;
}