源碼地址: GHApplicationMediatorgit
AppDelegate控制着App的主要生命週期,好比App初始化完成後構建主視圖,App接收到遠程消息回調,Url-Scheme回調,第三方SDK初始化,數據庫初始化等等。github
基於這個緣由,隨着App的版本迭代,AppDelegate中的代碼量會愈來愈大。當AppDelegate的代碼量到達必定程度時,咱們就該開始考慮將AppDelegate中的代碼進行模塊化封裝。數據庫
在考慮這個方案的時候,咱們的項目剛剛度過了原型期,使用的SDK並很少,業務需求也尚未起來。瀏覽器
在這個背景,我選擇用Category封裝AppDelegate的方案。網絡
建立一個AppDelegate+XXX的Category,好比下面這個AppDelegate+CEReachabilityapp
#import "AppDelegate.h"
@interface AppDelegate (CEReachability)
- (void)setupReachability;
@end
@implementation AppDelegate (CEReachability)
- (void)setupReachability
{
// Allocate a reachability object
Reachability *reach = [Reachability reachabilityWithHostname:kServerBaseUrl];
// Set the blocks
reach.reachableBlock = ^(Reachability *reach) {
if (reach.currentReachabilityStatus == ReachableViaWWAN) {
BLYLogInfo(@"ReachabilityStatusChangeBlock--->蜂窩數據網");
[CESettingsManager sharedInstance].needNoWifiAlert = YES;
} else if (reach.currentReachabilityStatus == ReachableViaWiFi) {
BLYLogInfo(@"ReachabilityStatusChangeBlock--->WiFi網絡");
[CESettingsManager sharedInstance].needNoWifiAlert = NO;
}
};
reach.unreachableBlock = ^(Reachability *reach) {
BLYLogInfo(@"ReachabilityStatusChangeBlock--->未知網絡狀態");
};
// Start the notifier, which will cause the reachability object to retain itself!
[reach startNotifier];
}
複製代碼
而後在AppDelegate中註冊這個模塊模塊化
#import "AppDelegate+CEReachability.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupReachability];
return YES;
}
複製代碼
有同窗可能會問,爲何不直接在Category中實現UIApplicationDelegate的方法。函數
同時import多個Category,而且多個Category都實現了同一個方法(例如 :- (void)applicationWillResignActive:(UIApplication *)application
),在調用該方法時選用哪一個實現是由Category文件的編譯順序來決定(在Build Phases > Complie Sources中指定),最後一個編譯的Category文件的方法實現將被使用。與import順序無關,實際上,當有兩個Category實現了同一個方法,不管你imprt的是那個Category,方法的實際實現永遠是編譯順序在最後的Category文件的方法實現。ui
優勢:atom
缺點:
隨着業務需求的增長,第三方支付、IM、各類URL-Scheme配置逐漸增長,特別是Open Url和Push Notifications須要有依賴關係,方案一很快就不能知足需求了,各類奇怪的註冊方式交織在一塊兒。
迫於求生欲,我決定第二次重構。
此次重構初始動機是因爲Category之間的互斥關係,有依賴流程的流程就必須寫在AppDelegate中。(好比Open Url,第三方支付用到了,瀏覽器跳轉也用到了)
因而,我增長了ApplicationMediator來管理AppDelegate與模塊的通訊,實現消息轉發到模塊的邏輯。
ApplicationMediator是一個單例,用於管理模塊的註冊與移除。
@interface CEApplicationMediator : UIResponder<UIApplicationDelegate, UNUserNotificationCenterDelegate>
@property (nonatomic, strong) NSHashTable *applicationModuleDelegates;
+ (instancetype)sharedInstance;
+ (void)registerAppilgationModuleDelegate:(id<UIApplicationDelegate>)moduleDelegate;
+ (void)registerNotificationModuleDelegate:(id<UIApplicationDelegate,UNUserNotificationCenterDelegate>)moduleDelegate;
+ (BOOL)removeModuleDelegateByClass:(Class)moduleClass;
@property (nonatomic, assign) UNNotificationPresentationOptions defaultNotificationPresentationOptions;
@end
複製代碼
模塊根據須要實現UIApplicationDelegate與UNUserNotificationCenterDelegate就能夠加入到UIApplication的生命週期中。
@implementation CEAMWindowDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.backgroundColor = [UIColor whiteColor];
// 須要將Window賦值給AppDelegate,有多時候會用全局AppDelegate去獲取Window。
[UIApplication sharedApplication].delegate.window = window;
CELaunchPageViewController *launchVC = [[CELaunchPageViewController alloc] init];
window.rootViewController = launchVC;
[window makeKeyAndVisible];
return YES;
}
@end
複製代碼
@implementation CEAMReachabilityDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Allocate a reachability object
Reachability *reach = [Reachability reachabilityWithHostname:kServerBaseUrl];
// Set the blocks
reach.reachableBlock = ^(Reachability *reach) {
if (reach.currentReachabilityStatus == ReachableViaWWAN) {
BLYLogInfo(@"ReachabilityStatusChangeBlock--->蜂窩數據網");
} else if (reach.currentReachabilityStatus == ReachableViaWiFi) {
BLYLogInfo(@"ReachabilityStatusChangeBlock--->WiFi網絡");
}
};
reach.unreachableBlock = ^(Reachability *reach) {
BLYLogInfo(@"ReachabilityStatusChangeBlock--->未知網絡狀態");
};
[reach startNotifier];
return YES;
}
@end
複製代碼
當模塊建立完成後,進行註冊後便可生效。
@implementation AppDelegate
+ (void)load
{
// CoreData
[CEApplicationMediator registerAppilgationModuleDelegate:[[CEAMCoreDataDelegate alloc] init]];
// ...
}
@end
複製代碼
這裏有兩種方式進行註冊
兩種方式均可以,各有利弊
我採用的是AppDelegate中註冊的方式,主要是準備將ApplicationMediator做爲組件使用。
做爲一個鍵盤俠,個人打字速度仍是很快的,不出五分鐘我已經寫完了五個UIApplicationDelegate中主要生命週期函數的手動轉發,可是當我打開UIApplicationDelegate頭文件後,我就矇蔽了,delegate的方法多到讓我頭皮發麻。
嗯,是的,因此消息轉發機制就在這種時候排上了大用處。
AppDelegate的全部方法都轉由ApplicationMediator處理,模塊轉發邏輯後面介紹。
@implementation AppDelegate
+ (void)load
{
//註冊模塊
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [[CEApplicationMediator sharedInstance] respondsToSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [[CEApplicationMediator sharedInstance] methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[[CEApplicationMediator sharedInstance] forwardInvocation:anInvocation];
}
@end
複製代碼
這樣AppDelegate就只須要處理註冊模塊就能夠了。
#pragma mark- Handle Method
/** 沒法經過[super respondsToSelector:aSelector]來檢測對象是否從super繼承了方法。 所以調用[super respondsToSelector:aSelector],至關於調用了[self respondsToSelector:aSelector] **/
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL result = [super respondsToSelector:aSelector];
if (!result) {
result = [self hasDelegateRespondsToSelector:aSelector];
}
return result;
}
/** 此方法還被用於當NSInvocation被建立的時候,好比在消息傳遞的時候。 若是當前Classf能夠處理未被直接實現的方法,則必須覆寫此方法。 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
id delegate = [self delegateRespondsToSelector:aSelector];
if (delegate) {
return [delegate methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
/** 沒法識別的消息處理 */
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
__block BOOL isExec = NO;
NSMethodSignature *methodSignature = anInvocation.methodSignature;
const char *returnType = methodSignature.methodReturnType;
// 沒有返回值,或者默認返回YES
if (0 == strcmp(returnType, @encode(void)) ||
anInvocation.selector == @selector(application:didFinishLaunchingWithOptions:)) {
[self notifySelectorOfAllDelegates:anInvocation.selector nofityHandler:^(id delegate) {
[anInvocation invokeWithTarget:delegate];
isExec = YES;
}];
} else if (0 == strcmp(returnType, @encode(BOOL))) {
// 返回值爲BOOL
[self notifySelectorOfAllDelegateUntilSuccessed:anInvocation.selector defaultReturnValue:NO nofityHandler:^BOOL(id delegate) {
[anInvocation invokeWithTarget:delegate];
// 得到返回值
NSUInteger returnValueLenth = anInvocation.methodSignature.methodReturnLength;
BOOL *retValue = (BOOL *)malloc(returnValueLenth);
[anInvocation getReturnValue:retValue];
BOOL result = *retValue;
return result;
}];
} else {
// 等同於[self doesNotRecognizeSelector:anInvocation.selector];
[super forwardInvocation:anInvocation];
}
}
- (BOOL)hasDelegateRespondsToSelector:(SEL)selector
{
__block BOOL result = NO;
[self.applicationModuleDelegates enumerateObjectsUsingBlock:^(id _Nonnull delegate, NSUInteger idx, BOOL * _Nonnull stop) {
if ([delegate respondsToSelector:selector]) {
result = YES;
*stop = YES;
}
}];
return result;
}
- (id)delegateRespondsToSelector:(SEL)selector
{
__block id resultDelegate;
[self.applicationModuleDelegates enumerateObjectsUsingBlock:^(id _Nonnull delegate, NSUInteger idx, BOOL * _Nonnull stop) {
if ([delegate respondsToSelector:selector]) {
resultDelegate = delegate;
*stop = YES;
}
}];
return resultDelegate;
}
/** 通知全部delegate響應方法 @param selector 響應方法 @param nofityHandler delegated處理調用事件 */
- (void)notifySelectorOfAllDelegates:(SEL)selector nofityHandler:(void(^)(id delegate))nofityHandler
{
if (_applicationModuleDelegates.count == 0) {
return;
}
[self.applicationModuleDelegates enumerateObjectsUsingBlock:^(id _Nonnull delegate, NSUInteger idx, BOOL * _Nonnull stop) {
if ([delegate respondsToSelector:selector]) {
if (nofityHandler) {
nofityHandler(delegate);
}
}
}];
}
/** 通知全部的delegate,當有delegate響應爲成功後,中斷通知。 @param selector 響應方法 @param defaultReturnValue 默認返回值(當設置爲YES時,即便沒有響應對象也會返回YES。) @param nofityHandler delegate處理調用事件 @return delegate處理結果 */
- (BOOL)notifySelectorOfAllDelegateUntilSuccessed:(SEL)selector defaultReturnValue:(BOOL)defaultReturnValue nofityHandler:(BOOL(^)(id delegate))nofityHandler
{
__block BOOL success = defaultReturnValue;
if (_applicationModuleDelegates.count == 0) {
return success;
}
[self.applicationModuleDelegates enumerateObjectsUsingBlock:^(id _Nonnull delegate, NSUInteger idx, BOOL * _Nonnull stop) {
if ([delegate respondsToSelector:selector]) {
if (nofityHandler) {
success = nofityHandler(delegate);
if (success) {
*stop = YES;
}
}
}
}];
return success;
}
複製代碼
這裏簡單說一下消息轉發的流程。
- (BOOL)respondsToSelector:(SEL)aSelector
在調用協議方法前,會檢測對象是否實現協議方法,若是響應則會調用對應的方法。- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
當調用的方法沒法找到時,若是未實現此方法,系統就會調用NSObject的doesNotRecognizeSelector方法,即拋出異常並Crash。當實現了這個方法是,系統會要求返回selector的對應方法實現,這裏就能夠開啓消息轉發。- (void)forwardInvocation:(NSInvocation *)anInvocation
當方法完成轉發設定後,會進入這個方法,由咱們來控制方法的執行。在步驟三裏,實現了自定義的轉發方案:
application:didFinishLaunchingWithOptions:
這種只返回YES的方法,轉發的時候,進行輪詢通知。實現了2.0版本後,新增模塊已經比較方便了,不過還有不少值得改進的地方。
[UIApplication sharedApplication].delegate.window.bounds.size
來獲取屏幕尺寸,因此在建立或更改Window的時候,須要牢記將Window賦值給AppDelegate。目前只經過了文檔約束,後續還會進行改進。