AppDelegate瘦身之服务化

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


image.png

来一段一般的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这个类代码极限缩减。

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


image.png

按照上图,以后我们的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.最后,你就可以运行看结果了。

image.png

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 在实际的开发过程中,AppDelegate应该除了负责应用生命周期之外,不应该再有多余的责任。但是往往在一个项目的...
    东了个尼阅读 2,270评论 0 6
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 这部剧讲述了当杀伐成为一种天下共势,君王们为逐鹿天下,而与刺客们之间展开的情义和复杂的权谋之争。 剧的设定很像是春...
    公子川1234阅读 1,762评论 0 2
  • 明明自己就是个任人宰割的小白鼠,还把自己幻想成拯救世界的奥特曼。 今天,遇到了和我同名次的竞争对手,他讲的很不错,...
    纪安安阅读 158评论 0 0
  • 30分钟入门Java8之方法引用 前言 之前两篇文章分别介绍了Java8的lambda表达式和默认方法和静态接口方...
    soberbad阅读 626评论 2 4