有沒有以爲你的 AppDelegate 太過龐大了?一個 iOS
應用可能集成了大量的服務,第三方服務、推送服務等等,大多數服務功能彼此獨立,想不想把它們完全從 AppDelegate 中拆出來?git
AppDelegate 並不遵循單一功能原則,它要負責處理不少事情,如應用生命週期回調、遠程推送、本地推送、應用跳轉(HandleOpenURL);若是集成了第三方服務,大多數還須要在應用啓動時初始化,而且須要處理應用跳轉,若是在 AppDelegate 中作這些事情,勢必讓它變得很龐大。github
不一樣服務的代碼糾纏在一塊兒,使得 AppDelegate 變得很難複用。並且若是你想要添加一個服務或者關閉一個服務,都須要去修改 AppDelegate。不少服務看起來互相獨立,並不依賴其它服務,咱們能夠把它們拆分出來,放在單獨的文件裏。微信
這個面向服務,應該達成下面這兩個要求:架構
添加或者刪除一個服務的時候,不須要更改 AppDelegate 中的任何一行代碼。app
AppDelegate 不實現 UIApplicationDelegate 協議中的方法,由協議去實現函數
第一點是要求實現可插拔特性。關於第二點,可能比較粗暴簡單的作法是在 AppDelegate 裏面實現全部的 UIApplicationDelegate 代理方法,而後在方法中把消息轉發給消息。這種作法有一些弊端:fetch
很明顯,AppDelegate 顯得比較笨重。ui
被空的代理實現綁架。有一些代理方法實現之後,須要在 Info.plist 中聲明支持相應的功能的,好比 backgroud remote notifications,不然可能會在控制檯看到下面的日誌:atom
You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
收到警告郵件。應用上架時蘋果還會檢查這些代理方法,好比遠程推送。假如 AppDelegate 實現了遠程推送相關的代理方法,可是並無調用註冊遠程推送的方法,也沒有申請推送證書,可能就會收到一封警告郵件。spa
既然實現全部的代理方法就只是爲了轉發消息,那有沒有方法可以聚合這些消息呢?答案是,有,具體實現請看下文。
iOS 應用程序在執行 main 方法以前,還作了不少事情,其中包括加載類。一個類在被加載時,它的 +load 方法會被調用。重寫每一個 Service 類 +load 方法,這個方法執行時註冊 Service。那服務要如何實現,如何啓動呢?
一般狀況下,你須要在 AppDelegate 中實現每個須要用到的代理方法,在這些代理方法中,調用不少不一樣的服務。可是上面第二點對咱們提出要求:只能由各個服務去實現它須要的代理方法。這裏我利用了 Objective-C 的消息轉發機制,把 AppDelegate 不能處理的消息轉發給各個服務。
每個代理方法被調用前,調用者會先調用 -respondsToSelector:,檢查代理能不能響應這個方法,AppDelegate 也不例外。咱們能夠重寫 -respondsToSelector:,告訴調用者 AppDelegate 能夠響應這個方法,但實際上 AppDelegate 並無實現這個方法。
接下來,調用者就會調用這個並無實現的代理方法,而後進入消息轉發流程,調用 -forwardInvocation: 方法。在這個方法中,咱們能夠把這個消息轉發到實現了對應代理方法的 Service 對象上。
重寫 - (void)forwardInvocation:(NSInvocation *)anInvocation 這個方法,咱們就能夠在全部實現了 UIApplicationDelegate 協議方法的 Service 對象上執行被調用的代理方法。這樣 AppDelegate 就再也不須要真正實現 UIApplicationDelegate 協議裏的方法了。
好了,無論怎麼說,都要落實到代碼上。爲了方便理解,我去掉了不少錯誤檢查代碼。具體實現和示例請看 github 上的這個版本。
首先是 MLSOAppDelegate.h
#import <UIKit/UIKit.h> @protocol MLAppService <UIApplicationDelegate> @required - (NSString *)serviceName; @end @interface MLSOAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; + (void)registerService:(id<MLAppService>)service @end
而後是 MLSOAppDelegate.m。判斷 Service 對象可否響應代理方法的依據是,能獲取到方法的真正的實現(IMP)。由於消息轉發機制的存在,獲取一個沒真正實現的方法的 IMP 的時候,會獲得 _objc_msgForward 這個函數,所以咱們須要排除它。
@implementation MLSOAppDelegate - (BOOL)respondsToSelector:(SEL)aSelector { __block IMP imp = [self methodForSelector:aSelector]; BOOL canResponse = (imp != NULL && imp != _objc_msgForward); if (! canResponse) { [_servicesMap enumerateKeysAndObjectsUsingBlock: ^(NSString * _Nonnull key, id<MLAppService> _Nonnull obj, BOOL * _Nonnull stop) { if ([obj respondsToSelector:aSelector]) { imp = [(id)obj methodForSelector:aSelector]; *stop = YES; } }]; canResponse = (imp != NULL && imp != _objc_msgForward); } return canResponse; } - (void)forwardInvocation:(NSInvocation *)anInvocation { [self.servicesMap enumerateKeysAndObjectsUsingBlock: ^(NSString * _Nonnull key, id<MLAppService> _Nonnull service, BOOL * _Nonnull stop) { if ( ! [service respondsToSelector:anInvocation.selector]) { return; } [anInvocation invokeWithTarget:service]; }]; } @end
上面講了如何實現 SOAppDelegate,那在項目中要怎麼使用呢?
首先,MLSOAppDelegate 能夠直接在 main 函數中使用:
#import <MLSOAppDelegate/MLSOAppDelegate.h> int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MLSOAppDelegate class])); } }
可是 MLSOAppDelegate 並無實現 -application:didFinishLaunchingWithOptions: 方法,應用在哪兒手動初始化 UI 呢?咱們能夠新建一個類 RootUIService:
#import "MLSOAppDelegate.h" @interface RootUIService : NSObject <MLAppService> @end @implementation RootUIService + (void)load { [MLSOAppDelegate registerService:[[RootUIService alloc] init]]; } - (NSString *)serviceName { return @"rootUI"; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; application.delegate.window = window; ViewController* dvc = [[ViewController alloc] init]; UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:dvc]; window.rootViewController = nav; [window makeKeyAndVisible]; return YES; } @end
前面介紹了一下簡單的服務的實現,如今再來看一個稍微複雜點的服務的實現:遠程推送服務。
#import "MLSOAppDelegate.h" @interface NotificationService : NSObject <MLAppService> @end @implementation NotificationService + (void)load { [MLSOAppDelegate registerService:[[NotificationService alloc] init]]; } - (NSString *)serviceName { return @"notifcation"; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) { NSLog(@"App was launched by remote notification."); } else if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) { NSLog(@"App was launched by local notification."); } [self registerUserNotifications]; return YES; } - (void)registerUserNotifications { UIUserNotificationType types = (UIUserNotificationTypeBadge| UIUserNotificationTypeSound| UIUserNotificationTypeAlert); UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; } - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSLog(@"%@ %@", NSStringFromSelector(_cmd), deviceToken); // upload the deviceToken to your servers } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { NSLog(@"%@ %@", NSStringFromSelector(_cmd), error); } - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { NSLog(@"%@ %@", NSStringFromSelector(_cmd), notificationSettings); [application registerForRemoteNotifications]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { NSLog(@"%@ %@", NSStringFromSelector(_cmd), userInfo); } @end
上面的代碼可能會讓你感到疑惑:RootUIService 和 NotificationService 兩個類都實現了 application:didFinishLaunchingWithOptions: 方法,程序在運行的時候究竟調用哪個?
答案是,都會調用,可是調用順序是不肯定的。
有的應用中有一些啓動代碼必須放在其它代碼前執行,你可能會想到下面這個解決方法,繼承 MLSOAppDelegate:
@interface AppDelegate : MLSOAppDelegate @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 在其它服務執行前,作一些事情 // ... // 這裏調用 super 方法,是爲了服務的代理實現可以正常執行 if ([super respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) { [super application:application didFinishLaunchingWithOptions:launchOptions]; } return YES; } @end
注意調用 super 方法的方式。 有些服務可能會實現 application:didFinishLaunchingWithOptions: 這個方法,調用 super 方法,能夠保證這些服務的代理方法可以正常執行。
這個方案使得開啓一些服務(好比遠程推送)變得簡單,只須要把 NotificationService 這個類加到工程中就能夠,不須要修改 AppDelegate 任何一行代碼,重用 NotificationService 也變得簡單。可是還存在一些問題,在執行服務實現的代理方法的時候,順序不可控。
最後再貼一下源碼連接:https://github.com/alexsun/ML...
相關文章
Objective-C Runtime 之動態方法解析實踐
使用 FlowControllers 改進iOS應用架構
做者信息
原文做者系力譜宿雲 LeapCloud 旗下MaxLeap團隊_UX成員:孫進【原創】
首發地址:https://blog.maxleap.cn/archi...
孫進,現任職於 MaxLeap UX 團隊,負責 MaxLeap iOS 端 SDK 開發,爲開發者提供好用,穩定的產品。此前作過兩年 iOS 應用開發,如今正嘗試 React Native 開發。
活動預告
報名連接:http://t.cn/Rt9ooRw
對咱們的技術乾貨/活動有興趣的小夥伴,請掃一下二維碼,關注咱們的微信公衆號!