源码地址: 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。目前只经过了文档约束,后续还会进行改进。