Refactoring Massive App Delegate

3 minute read Published: 2021-02-01

Refactoring Massive App Delegate

실제 production app 을 개발하다 보면 AppDelegate 클래스가 쉽게 비대해지고 파일수가 어마어마하게 늘어나는 경우가 비일비재하다. 이를 refactoring 하는 방법

Mediator Design Pattern

refactoring-massive-app-delegate 에 보면 다양한 디자인 패턴으로 appDelegate를 refactoring 하는 법이 나와있다 그중 mediator design pattern 으로 실제 활용한 예

AppLifecycle Mediator

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 구현

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()
 }
}

extension AppLifecycleMediator {
  @objc
  static func makeDefaultMediator() -> AppLifecycleMediator {
    let locationListener = AppLocationListener()
    let debugListener = AppDebugListener()

    return AppLifecycleMediator(listeners: [debugListener, locationListener])
  }
}

add mediator to AppDelegate

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate {

   var window: UIWindow?

   let mediator = AppLifecycleMediator.makeDefaultMediator() // 여기에 추가

   func application(_ application: UIApplication,                                           didFinishLaunchingWithOptions launchOptions:                              [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

      return true

   }
}
@interface AppDelegate ()<CLLocationManagerDelegate, UNUserNotificationCenterDelegate, FIRMessagingDelegate, AppsFlyerLibDelegate>
{
  AppLifecycleMediator *_mediator; // 여기에 선언하고 
}


...
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  _mediator = [AppLifecycleMediator makeDefaultMediator]; // 구현추가.
  return YES;
}

Reference