有没有以为你的 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
对咱们的技术干货/活动有兴趣的小伙伴,请扫一下二维码,关注咱们的微信公众号!