AppDelegate瘦身之服務(wù)化

有沒有覺得你的AppDelegate雜亂無章静陈?代碼幾百行上千行燕雁?集成了無數(shù)的功能,如推送鲸拥、埋點(diǎn)贵白、日志統(tǒng)計、Crash統(tǒng)計等等崩泡,感覺AppDelegate無所不能。


image.png

來一段一般的AppDelegate代碼猬膨,來自網(wǎng)上一篇文章:

@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
        }
    }
}

看完后角撞,有沒有一個類就能完成整個應(yīng)用的想法?今天勃痴,我們的目的就是使得AppDelegate這個類代碼極限縮減谒所。

如果大家有了解過微服務(wù)的話,大家就會知道沛申,一個服務(wù)專職做一件事情劣领,然后由網(wǎng)關(guān)來調(diào)度,這樣的邏輯是非常清晰的铁材,也非常便于維護(hù)尖淘,我們這次的改造也是源于這樣的思路的。


image.png

按照上圖著觉,以后我們的AppDelegate只做網(wǎng)關(guān)對應(yīng)的功能村生,其他具體業(yè)務(wù),交由不同的服務(wù)去做饼丘,那么趁桃,我們應(yīng)該如何實現(xiàn)這樣的想法呢?

1.首先我們創(chuàng)建一個文件TDWApplicationDelegate.swift
里面的代碼:

/// UIApplicationDelegate 協(xié)議擴(kuò)展
public protocol TDWApplicationDelegate: UIApplicationDelegate {
    
}

這里定義了一個TDWApplicationDelegate,繼承UIApplicationDelegate卫病。這個協(xié)議是方便以后擴(kuò)展用的油啤。

2.我們再創(chuàng)建一個文件TDWAppDelegateService.swift
代碼為:

import Foundation

open class TDWAppDelegateService: UIResponder, TDWApplicationDelegate {

    /// 啟動服務(wù)的數(shù)組
    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: - 其他應(yīng)用喚起
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: - 推送相關(guān)
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相關(guān)
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.定義了一個服務(wù)數(shù)組益咬,把服務(wù)都統(tǒng)一管理。
2.在extension里面實現(xiàn)常用的AppDelegate生命周期的協(xié)議屹逛。因為__services里面的服務(wù)都是繼承于TDWApplicationDelegate础废,所以,沒有服務(wù)罕模,其實能實現(xiàn)AppDelegate生命周期评腺。所以,在這個TDWAppDelegateService上淑掌,我在他所有的生命周期里同步遍歷調(diào)用所有服務(wù)__services的對等生命周期蒿讥,這樣,就變相于我收到系統(tǒng)的信息后抛腕,會同步給各個服務(wù)芋绸,讓他們自己處理了。

這樣担敌,我們就完成了整個服務(wù)的框架了摔敛。那么,我們?nèi)绾问褂媚兀?br> 這里全封,我以2個服務(wù)作為例子马昙,當(dāng)然,你可以構(gòu)建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個服務(wù),一個是初始化服務(wù)土匀,一個是日志服務(wù)子房,他們都只做一件事件,打印相關(guān)的字符串就轧。

ok证杭,下面我們構(gòu)建下我們的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方法躯砰,把服務(wù)實例放到__services數(shù)組就可以了。
3.最后携丁,你就可以運(yùn)行看結(jié)果了琢歇。

image.png

沒錯兰怠,服務(wù)按順序執(zhí)行對應(yīng)的功能,也就是打印對應(yīng)的字符串李茫。

好了揭保,以上就是本文要介紹的內(nèi)容,歡迎評論反饋魄宏,謝謝=章隆!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宠互,一起剝皮案震驚了整個濱河市味榛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌予跌,老刑警劉巖搏色,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異券册,居然都是意外死亡频轿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門烁焙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來航邢,“玉大人,你說我怎么就攤上這事骄蝇∩乓螅” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵九火,是天一觀的道長赚窃。 經(jīng)常有香客問我,道長吃既,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任跨细,我火速辦了婚禮鹦倚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冀惭。我一直安慰自己震叙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布散休。 她就那樣靜靜地躺著媒楼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戚丸。 梳的紋絲不亂的頭發(fā)上划址,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天扔嵌,我揣著相機(jī)與錄音,去河邊找鬼夺颤。 笑死痢缎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的世澜。 我是一名探鬼主播独旷,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寥裂!你這毒婦竟也來了嵌洼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤封恰,失蹤者是張志新(化名)和其女友劉穎麻养,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俭驮,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡回溺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了混萝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遗遵。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逸嘀,靈堂內(nèi)的尸體忽然破棺而出车要,到底是詐尸還是另有隱情,我是刑警寧澤崭倘,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布翼岁,位于F島的核電站,受9級特大地震影響司光,放射性物質(zhì)發(fā)生泄漏琅坡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一残家、第九天 我趴在偏房一處隱蔽的房頂上張望榆俺。 院中可真熱鬧,春花似錦坞淮、人聲如沸茴晋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诺擅。三九已至,卻和暖如春啡直,著一層夾襖步出監(jiān)牢的瞬間烁涌,已是汗流浹背苍碟。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烹玉,地道東北人驰怎。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像二打,于是被迫代替她去往敵國和親县忌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在實際的開發(fā)過程中继效,AppDelegate應(yīng)該除了負(fù)責(zé)應(yīng)用生命周期之外症杏,不應(yīng)該再有多余的責(zé)任。但是往往在一個項目的...
    東了個尼閱讀 2,332評論 0 6
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,097評論 1 32
  • 這部劇講述了當(dāng)殺伐成為一種天下共勢瑞信,君王們?yōu)橹鹇固煜吕鞑c刺客們之間展開的情義和復(fù)雜的權(quán)謀之爭。 劇的設(shè)定很像是春...
    公子川1234閱讀 2,221評論 0 2
  • 明明自己就是個任人宰割的小白鼠帜乞,還把自己幻想成拯救世界的奧特曼。 今天筐眷,遇到了和我同名次的競爭對手黎烈,他講的很不錯,...
    紀(jì)安安閱讀 172評論 0 0
  • 30分鐘入門Java8之方法引用 前言 之前兩篇文章分別介紹了Java8的lambda表達(dá)式和默認(rèn)方法和靜態(tài)接口方...
    soberbad閱讀 638評論 2 4