当前位置:   article > 正文

iOS - 更轻量级的 AppDelegate - 面向服务设计

mlsoappdelegate

有没有觉得你的 AppDelegate 太过庞大了?一个 iOS
应用可能集成了大量的服务,第三方服务、推送服务等等,大多数服务功能彼此独立,想不想把它们彻底从 AppDelegate 中拆出来?

clipboard.png

AppDelegate 做了太多事

AppDelegate 并不遵循单一功能原则,它要负责处理很多事情,如应用生命周期回调、远程推送、本地推送、应用跳转(HandleOpenURL);如果集成了第三方服务,大多数还需要在应用启动时初始化,并且需要处理应用跳转,如果在 AppDelegate 中做这些事情,势必让它变得很庞大。

不同服务的代码纠缠在一起,使得 AppDelegate 变得很难复用。而且如果你想要添加一个服务或者关闭一个服务,都需要去修改 AppDelegate。很多服务看起来互相独立,并不依赖其它服务,我们可以把它们拆分出来,放在单独的文件里。

要实现的目标

这个面向服务,应该达成下面这两个要求:

  1. 添加或者删除一个服务的时候,不需要更改 AppDelegate 中的任何一行代码。

  2. AppDelegate 不实现 UIApplicationDelegate 协议中的方法,由协议去实现

第一点是要求实现可插拔特性。关于第二点,可能比较粗暴简单的做法是在 AppDelegate 里面实现所有的 UIApplicationDelegate 代理方法,然后在方法中把消息转发给消息。这种做法有一些弊端:

  1. 很明显,AppDelegate 显得比较笨重。

  2. 被空的代理实现绑架。有一些代理方法实现以后,需要在 Info.plist 中声明支持相应的功能的,比如 backgroud remote notifications,否则可能会在控制台看到下面的日志:

    1. You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:],
    2. but you still need to add "remote-notification" to the list of
    3. your supported UIBackgroundModes in your Info.plist.
  3. 收到警告邮件。应用上架时苹果还会检查这些代理方法,比如远程推送。假如 AppDelegate 实现了远程推送相关的代理方法,但是并没有调用注册远程推送的方法,也没有申请推送证书,可能就会收到一封警告邮件。

既然实现所有的代理方法就只是为了转发消息,那有没有方法能够聚合这些消息呢?答案是,有,具体实现请看下文。

如何实现?

+load

iOS 应用程序在执行 main 方法之前,还做了很多事情,其中包括加载类。一个类在被加载时,它的 +load 方法会被调用。重写每个 Service 类 +load 方法,这个方法执行时注册 Service。那服务要如何实现,如何启动呢?

-respondsToSelector:

通常情况下,你需要在 AppDelegate 中实现每一个需要用到的代理方法,在这些代理方法中,调用很多不同的服务。但是上面第二点对我们提出要求:只能由各个服务去实现它需要的代理方法。这里我利用了 Objective-C 的消息转发机制,把 AppDelegate 不能处理的消息转发给各个服务。

每一个代理方法被调用前,调用者会先调用 -respondsToSelector:,检查代理能不能响应这个方法,AppDelegate 也不例外。我们可以重写 -respondsToSelector:,告诉调用者 AppDelegate 可以响应这个方法,但实际上 AppDelegate 并没有实现这个方法。

-forwardInvocation:

接下来,调用者就会调用这个并没有实现的代理方法,然后进入消息转发流程,调用 -forwardInvocation: 方法。在这个方法中,我们可以把这个消息转发到实现了对应代理方法的 Service 对象上。

重写 - (void)forwardInvocation:(NSInvocation *)anInvocation 这个方法,我们就可以在所有实现了 UIApplicationDelegate 协议方法的 Service 对象上执行被调用的代理方法。这样 AppDelegate 就不再需要真正实现 UIApplicationDelegate 协议里的方法了。

上代码

好了,不管怎么说,都要落实到代码上。为了方便理解,我去掉了很多错误检查代码。具体实现和示例请看 github 上的这个版本

首先是 MLSOAppDelegate.h

  1. #import <UIKit/UIKit.h>
  2. @protocol MLAppService <UIApplicationDelegate>
  3. @required
  4. - (NSString *)serviceName;
  5. @end
  6. @interface MLSOAppDelegate : UIResponder <UIApplicationDelegate>
  7. @property (strong, nonatomic) UIWindow *window;
  8. + (void)registerService:(id<MLAppService>)service
  9. @end

然后是 MLSOAppDelegate.m。判断 Service 对象能否响应代理方法的依据是,能获取到方法的真正的实现(IMP)。因为消息转发机制的存在,获取一个没真正实现的方法的 IMP 的时候,会得到 _objc_msgForward 这个函数,因此我们需要排除它。

  1. @implementation MLSOAppDelegate
  2. - (BOOL)respondsToSelector:(SEL)aSelector {
  3. __block IMP imp = [self methodForSelector:aSelector];
  4. BOOL canResponse = (imp != NULL && imp != _objc_msgForward);
  5. if (! canResponse) {
  6. [_servicesMap enumerateKeysAndObjectsUsingBlock:
  7. ^(NSString * _Nonnull key, id<MLAppService> _Nonnull obj, BOOL * _Nonnull stop)
  8. {
  9. if ([obj respondsToSelector:aSelector]) {
  10. imp = [(id)obj methodForSelector:aSelector];
  11. *stop = YES;
  12. }
  13. }];
  14. canResponse = (imp != NULL && imp != _objc_msgForward);
  15. }
  16. return canResponse;
  17. }
  18. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  19. [self.servicesMap enumerateKeysAndObjectsUsingBlock:
  20. ^(NSString * _Nonnull key, id<MLAppService> _Nonnull service, BOOL * _Nonnull stop)
  21. {
  22. if ( ! [service respondsToSelector:anInvocation.selector]) {
  23. return;
  24. }
  25. [anInvocation invokeWithTarget:service];
  26. }];
  27. }
  28. @end

如何使用?

上面讲了如何实现 SOAppDelegate,那在项目中要怎么使用呢?

集成 MLSOAppDelegate

首先,MLSOAppDelegate 可以直接在 main 函数中使用:

  1. #import <MLSOAppDelegate/MLSOAppDelegate.h>
  2. int main(int argc, char * argv[]) {
  3. @autoreleasepool {
  4. return UIApplicationMain(argc, argv, nil, NSStringFromClass([MLSOAppDelegate class]));
  5. }
  6. }

手动初始化 window

但是 MLSOAppDelegate 并没有实现 -application:didFinishLaunchingWithOptions: 方法,应用在哪儿手动初始化 UI 呢?我们可以新建一个类 RootUIService:

  1. #import "MLSOAppDelegate.h"
  2. @interface RootUIService : NSObject <MLAppService>
  3. @end
  4. @implementation RootUIService
  5. + (void)load {
  6. [MLSOAppDelegate registerService:[[RootUIService alloc] init]];
  7. }
  8. - (NSString *)serviceName {
  9. return @"rootUI";
  10. }
  11. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  12. UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  13. application.delegate.window = window;
  14. ViewController* dvc = [[ViewController alloc] init];
  15. UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:dvc];
  16. window.rootViewController = nav;
  17. [window makeKeyAndVisible];
  18. return YES;
  19. }
  20. @end

再来看一个复杂点的服务

前面介绍了一下简单的服务的实现,现在再来看一个稍微复杂点的服务的实现:远程推送服务。

  1. #import "MLSOAppDelegate.h"
  2. @interface NotificationService : NSObject <MLAppService>
  3. @end
  4. @implementation NotificationService
  5. + (void)load {
  6. [MLSOAppDelegate registerService:[[NotificationService alloc] init]];
  7. }
  8. - (NSString *)serviceName {
  9. return @"notifcation";
  10. }
  11. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  12. if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
  13. NSLog(@"App was launched by remote notification.");
  14. } else if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {
  15. NSLog(@"App was launched by local notification.");
  16. }
  17. [self registerUserNotifications];
  18. return YES;
  19. }
  20. - (void)registerUserNotifications {
  21. UIUserNotificationType types = (UIUserNotificationTypeBadge|
  22. UIUserNotificationTypeSound|
  23. UIUserNotificationTypeAlert);
  24. UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
  25. [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
  26. }
  27. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  28. NSLog(@"%@ %@", NSStringFromSelector(_cmd), deviceToken);
  29. // upload the deviceToken to your servers
  30. }
  31. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  32. NSLog(@"%@ %@", NSStringFromSelector(_cmd), error);
  33. }
  34. - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
  35. NSLog(@"%@ %@", NSStringFromSelector(_cmd), notificationSettings);
  36. [application registerForRemoteNotifications];
  37. }
  38. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  39. NSLog(@"%@ %@", NSStringFromSelector(_cmd), userInfo);
  40. }
  41. @end

上面的代码可能会让你感到疑惑:RootUIService 和 NotificationService 两个类都实现了 application:didFinishLaunchingWithOptions: 方法,程序在运行的时候究竟调用哪一个?

答案是,都会调用,但是调用顺序是不确定的

继承 MLSOAppDelegate

有的应用中有一些启动代码必须放在其它代码前执行,你可能会想到下面这个解决方法,继承 MLSOAppDelegate:

  1. @interface AppDelegate : MLSOAppDelegate
  2. @end
  3. @implementation AppDelegate
  4. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  5. // 在其它服务执行前,做一些事情
  6. // ...
  7. // 这里调用 super 方法,是为了服务的代理实现能够正常执行
  8. if ([super respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) {
  9. [super application:application didFinishLaunchingWithOptions:launchOptions];
  10. }
  11. return YES;
  12. }
  13. @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 开发。


活动预告

clipboard.png

报名链接:http://t.cn/Rt9ooRw


对我们的技术干货/活动有兴趣的小伙伴,请扫一下二维码,关注我们的微信公众号!

clipboard.png

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/124957
推荐阅读
相关标签
  

闽ICP备14008679号