AppDelegate瘦身之服务化

有没有觉得你的AppDelegate杂乱无章?代码几百行上千行?集成了无数的功能,如推送、埋点、日志统计、Crash统计等等,感觉AppDelegate无所不能。

来一段一般的AppDelegate代码,来自网上一篇文章:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
 
    var window: UIWindow?
 
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        Log(info: "AppDelegate.didFinishLaunchingSite started.")
        application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
        
        UNUserNotificationCenter.current().register(
            delegate: self,
            actions: [UNNotificationAction(identifier: "favorite", title: .localized(.favorite))]
        )
        
        // Initialize Google Analytics
        if !AppGlobal.userDefaults[.googleAnalyticsID].isEmpty {
            GAI.sharedInstance().tracker(
                withTrackingId: AppGlobal.userDefaults[.googleAnalyticsID])
        }
        
        // Declare data format from remote REST API
        JSON.dateFormatter.dateFormat = ZamzamConstants.DateTime.JSON_FORMAT
        
        // Initialize components
        AppLogger.shared.setUp()
        AppData.shared.setUp()
        
        // Select home tab
        (window?.rootViewController as? UITabBarController)?.selectedIndex = 2
        
        setupTheme()
        
        Log(info: "App finished launching.")
        
        // Handle shortcut launch
        if let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
            performActionForShortcutItem(application, shortcutItem: shortcutItem)
            return false
        }
        
        return true
    }
    
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let webpageURL = userActivity.webpageURL else { return false }
        Log(info: "AppDelegate.continueUserActivity for URL: \(webpageURL.absoluteString).")
        return navigateByURL(webpageURL)
    }
    
    func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        Log(info: "AppDelegate.performFetch started.")
        scheduleUserNotifications(completionHandler: completionHandler)
    }
    
    func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
        window?.rootViewController?.dismiss(animated: false, completion: nil)
        guard let tabController = window?.rootViewController as? UITabBarController else { completionHandler?(false); return }
        
        switch shortcutItem.type {
        case "favorites":
            tabController.selectedIndex = 0
        case "search":
            tabController.selectedIndex = 3
        case "contact":
            guard let url = URL(string: "mailto:\(AppGlobal.userDefaults[.email])") else { break }
            UIApplication.shared.open(url)
        default: break
        }
        
        completionHandler?(true)
    }
}
 
// MARK: - User Notification Delegate
 
extension AppDelegate {
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        guard let id = response.notification.request.content.userInfo["id"] as? Int,
            let link = response.notification.request.content.userInfo["link"] as? String,
            let url = try? link.asURL()
            else { return }
        
        switch response.actionIdentifier {
        case UNNotificationDefaultActionIdentifier: _ = navigateByURL(url)
        case "favorite": PostService().addFavorite(id)
        case "share": _ = navigateByURL(url)
        default: break
        }
        
        completionHandler()
    }
    
    private func scheduleUserNotifications(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        // Get latest posts from server
        // Persist network manager instance to ensure lifespan is not interrupted
        urlSessionManager = PostService().updateFromRemote {
            guard case .success(let results) = $0 else { return completionHandler(.failed) }
            
            guard let id = results.created.first,
                let post = (try? Realm())?.object(ofType: Post.self, forPrimaryKey: id)
                else { return completionHandler(.noData) }
            
            var attachments = [UNNotificationAttachment]()
            
            // Completion process on exit
            func deferred() {
                // Launch notification
                UNUserNotificationCenter.current().add(
                    body: post.previewContent,
                    title: post.title,
                    attachments: attachments,
                    timeInterval: 5,
                    userInfo: [
                        "id": post.id,
                        "link": post.link
                    ],
                    completion: {
                        guard $0 == nil else { return Log(error: "Could not schedule the notification for the post: \($0.debugDescription).") }
                        Log(debug: "Scheduled notification for post during background fetch.")
                }
                )
                
                completionHandler(.newData)
            }
            
            // Get remote media to attach to notification
            guard let link = post.media?.thumbnailLink else { return deferred() }
            let thread = Thread.current
            
            UNNotificationAttachment.download(from: link) {
                defer { thread.async { deferred() } }
                
                guard $0.isSuccess, let attachment = $0.value else {
                    return Log(error: "Could not download the post thumbnail (\(link)): \($0.error.debugDescription).")
                }
                
                // Store attachment to schedule notification later
                attachments.append(attachment)
            }
        }
    }
}
 
// MARK: - Internal functions
 
private extension AppDelegate {
    
    func setupTheme() {
        window?.tintColor = UIColor(rgb: AppGlobal.userDefaults[.tintColor])
        
        if !AppGlobal.userDefaults[.titleColor].isEmpty {
            UINavigationBar.appearance().titleTextAttributes = [
                NSAttributedStringKey.foregroundColor: UIColor(rgb: AppGlobal.userDefaults[.titleColor])
            ]
        }
        
        // Configure tab bar
        if let controller = window?.rootViewController as? UITabBarController {
            controller.tabBar.items?.get(1)?.image = UIImage(named: "top-charts", inBundle: AppConstants.bundle)
            controller.tabBar.items?.get(1)?.selectedImage = UIImage(named: "top-charts-filled", inBundle: AppConstants.bundle)
            controller.tabBar.items?.get(2)?.image = UIImage(named: "explore", inBundle: AppConstants.bundle)
            controller.tabBar.items?.get(2)?.selectedImage = UIImage(named: "explore-filled", inBundle: AppConstants.bundle)
            
            if !AppGlobal.userDefaults[.tabTitleColor].isEmpty {
                UITabBarItem.appearance().setTitleTextAttributes([
                    NSAttributedStringKey.foregroundColor: UIColor(rgb: AppGlobal.userDefaults[.tabTitleColor])
                    ], for: .selected)
            }
        }
        
        // Configure dark mode if applicable
        if AppGlobal.userDefaults[.darkMode] {
            UINavigationBar.appearance().barStyle = .black
            UITabBar.appearance().barStyle = .black
            UICollectionView.appearance().backgroundColor = .black
            UITableView.appearance().backgroundColor = .black
            UITableViewCell.appearance().backgroundColor = .clear
        }
    }
}
复制代码

看完后,有没有一个类就能完成整个应用的想法?今天,我们的目的就是使得AppDelegate这个类代码极限缩减。

如果大家有了解过微服务的话,大家就会知道,一个服务专职做一件事情,然后由网关来调度,这样的逻辑是非常清晰的,也非常便于维护,我们这次的改造也是源于这样的思路的。

按照上图,以后我们的AppDelegate只做网关对应的功能,其他具体业务,交由不同的服务去做,那么,我们应该如何实现这样的想法呢?

1.首先我们创建一个文件TDWApplicationDelegate.swift 里面的代码:

/// UIApplicationDelegate 协议扩展
public protocol TDWApplicationDelegate: UIApplicationDelegate {
    
}
复制代码

这里定义了一个***TDWApplicationDelegate***,继承***UIApplicationDelegate***。这个协议是方便以后扩展用的。

2.我们再创建一个文件TDWAppDelegateService.swift 代码为:

import Foundation

open class TDWAppDelegateService: UIResponder, TDWApplicationDelegate {

    /// 启动服务的数组
    open var __services:[TDWApplicationDelegate] = []
}

// MARK: - 启动
extension TDWAppDelegateService {
   open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        __services.forEach {
            _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions)
        }
        
        return true
    }
}

// MARK: - 其他应用唤起
extension TDWAppDelegateService {
    
    // iOS 9.0 及以下
    open func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
        __services.forEach {
            _ = $0.application?(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
        }
        return true
    }
    
    // iOS 9.0 以上
    open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        if #available(iOS 9.0, *) {
            __services.forEach {
                _ = $0.application?(app, open: url, options: options)
            }
            return true
        }else {
            return false
        }
    }
}

// MARK: - 前后台
extension TDWAppDelegateService {
    
    open func applicationWillEnterForeground(_ application: UIApplication) {
        __services.forEach { $0.applicationWillEnterForeground?(application) }
    }
    
    open func applicationDidEnterBackground(_ application: UIApplication) {
        __services.forEach { $0.applicationDidEnterBackground?(application) }
    }
    
    open func applicationDidBecomeActive(_ application: UIApplication) {
        __services.forEach { $0.applicationDidBecomeActive?(application) }
    }
    
    open func applicationWillResignActive(_ application: UIApplication) {
        __services.forEach { $0.applicationWillResignActive?(application) }
    }
    
    open func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        __services.forEach{ $0.application?(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)}
    }
}

// MARK: - 退出
extension TDWAppDelegateService {
    
    open func applicationWillTerminate(_ application: UIApplication) {
        __services.forEach { $0.applicationWillTerminate?(application) }
    }
    
    open func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
        __services.forEach { $0.applicationDidReceiveMemoryWarning?(application) }
    }
}

// MARK: - 推送相关
extension TDWAppDelegateService {
    
    open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        __services.forEach { $0.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) }
    }
    
    open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        __services.forEach { $0.application?(application, didFailToRegisterForRemoteNotificationsWithError: error) }
    }
    
    // NS_AVAILABLE_IOS(7_0);
    open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        __services.forEach { $0.application?(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)}
    }
}

// MARK: - 3DTouch相关
extension TDWAppDelegateService {
    @available(iOS 9.0, *)
    open func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
        __services.forEach { $0.application?(application, performActionFor: shortcutItem, completionHandler: completionHandler) }
    }
}
复制代码

这个是本文的核心类,他主要做了些什么事情呢?

1.定义了一个服务数组,把服务都统一管理。 2.在extension里面实现常用的AppDelegate生命周期的协议。因为***__services***里面的服务都是继承于***TDWApplicationDelegate***,所以,没有服务,其实能实现AppDelegate生命周期。所以,在这个***TDWAppDelegateService***上,我在他所有的生命周期里同步遍历调用所有服务***__services***的对等生命周期,这样,就变相于我收到系统的信息后,会同步给各个服务,让他们自己处理了。

这样,我们就完成了整个服务的框架了。那么,我们如何使用呢? 这里,我以2个服务作为例子,当然,你可以构建10个,只要你喜欢。

import TDWAppDelegateService

class TDWInitializeService: NSObject, TDWApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        print("TDWInitializeService")
        
        return true
    }
}

class TDWLogService: NSObject, TDWApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        print("TDWLogService")
        
        return true
    }
}
复制代码

这里有2个服务,一个是初始化服务,一个是日志服务,他们都只做一件事件,打印相关的字符串。

ok,下面我们构建下我们的AppDelegate:

import UIKit
import TDWAppDelegateService

@UIApplicationMain
class AppDelegate: TDWAppDelegateService {

    var window: UIWindow?


    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        __services = [TDWInitializeService(), TDWLogService()]
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }


}
复制代码

AppDelegate非常简洁,他只有短短几句代码。 1.首先AppDelegate继承于***TDWAppDelegateService*** 2.然后重载***didFinishLaunchingWithOptions***方法,把服务实例放到***__services***数组就可以了。 3.最后,你就可以运行看结果了。

没错,服务按顺序执行对应的功能,也就是打印对应的字符串。

好了,以上就是本文要介绍的内容,欢迎评论反馈,谢谢!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值