微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

iOS AppDelegate瘦身方案探索

2018-03-22 18:19 朱金辉

AppDelegate 瘦身是一个大家都很熟悉的话题,各家也有各自的解决方案。但方案无外乎两种,一种是从 AppDelegate 本身入手,通过各种方式减少 AppDelegate 的代码行数,另一种是通过架构层面就解决了。本文将分别介绍这两种方式的代表性库,并对比其优缺点。

FRDModuleManager

FRDModuleManager 是豆瓣开源的轻量级模块管理工具。它通过减小 AppDelegate 的代码量来把很多职责拆分至各个模块中去,这样 AppDelegate 会变得容易维护。其主要类 FRDModuleManager 只有 300 多行代码,使用方式也很简单:

  加载模块
NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"ModulesRegister" ofType:@"plist"];
FRDModuleManager *manager = [FRDModuleManager sharedInstance];
[manager loadModulesWithPlistFile:plistPath];
  在 UIApplicationDelegate 各方法中留下钩子
 NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"ModulesRegister" ofType:@"plist"];
FRDModuleManager *manager = [FRDModuleManager sharedInstance];
[manager loadModulesWithPlistFile:plistPath];
//... 这里省略其他的多个生命周期方法
  实现原理

FRDModuleManager 的实现很简单,其内部数组持有注册的模块的引用,通过依次调用数组中的每个模块的特定方法来达到解耦的目的:

优点

  • 简单,只需要几行代码就可以解决。

  • 被添加的每个模块都可以“享受”AppDelegate 的各个生命周期。

缺点

  • 每个模块都要初始化并分配内存,当 FRDModuleManager 里注册了大量模块时,会创建大量对象并影响 App 启动速度。

  • 缺少模块初始化优先级,当有三个模块 A,B,C 时,正好 C 依赖于 B,B 依赖于 A,如果在配置文件中配置 A,B,C 的顺序又是打乱时,初始化会出问题。

JSDecoupledAppDelegate

JSDecoupledAppDelegate 是由 JSBadgeView 的作者开发的一款轻量级的 AppDelegate 解耦工具,笔者的个人项目壁纸宝贝正在使用这个库。它将 AppDelegate 各个功能点独立出来,并通过代理的方式将控制权下发。我们可以看到 JSDecoupledAppDelegate 类中有很多代理,这边列举几个:

这些代理见名知意,例如 appStateDelegate 是用于处理 App 的各种状态(didFinishLaunchingWithOptions、applicationDidBecomeActive 等)下的逻辑;remoteNotificationsDelegate 是用于处理 App 的推送的逻辑。JSDecoupledAppDelegate 使用起来也非常简单:

1 . 将 main.m 中的 AppDelegate 替换成 JSDecoupledAppDelegate:

return UIApplicationMain(argc, argv, nil, NSStringFromClass([JSDecoupledAppDelegate class]));

2 . 指定你需要处理的各个 JSDecoupledAppDelegate 的子 delegate。例如,你需要实现didFinishLaunchingWithOptions 方法,则新建一个类,在其中添加

+ (void)load
{
    [JSDecoupledAppDelegate sharedAppDelegate].appStateDelegate = [[self alloc] init];
}

然后就可以在里面实现我们以前在 didFinishLaunchingWithOptions 的方法。

实现原理:

iOS 开发的小伙伴应该对 Objective-C 的消息转发机制有所了解,JSDecoupledAppDelegate 就是通过转发 AppDelegate 的各个方法来实现 AppDelegate 的解耦的:

优点:

  • JSDecoupledAppDelegate 本身对于 AppDelegate 各个功能的拆分对我们理解 AppDelegate 有一定帮助——AppDelegate 确实承载了太多的功能。

  • 由于各个子代理对象的执行顺序是确定的,因此基本可以解决 FRDModuleManager 中相互依赖的问题。

缺点:

JSDecoupledAppDelegate 的缺点非常明显:使用它必须废弃原生的 AppDelegate,因此我们不能通过

((AppDelegate *)[UIApplication sharedApplication].delegate).window

来获取 window,以及 window 的 rootViewController。这个问题笔者曾经提 issue 给作者,作者的回答也是只能通过获取 view.window 等曲线救国的方式获取 window。

AppDelegate 分类 (Category)

创建 AppDelegate 分类无疑是低投入高产出的最好解决方案了。目前笔者公司的项目正在使用该方式。不需要添加任何三方库,我们就可以给 AppDelegate 添加很多方法,并且能轻松控制方法的执行顺序,通过此方式,我们项目的 AppDelegate 的.m 文件文件大小成功瘦身至 200 行 +:

然而分类的缺点也不言而喻:添加新的属性比较繁琐,只能通过 runtime 或者 BlocksKit 等三方库实现。

以上三种方法都是通过对 AppDelegate 修改或添加的方式来达到降低耦合的,下面介绍几种从 App 架构层就降低 AppDelegate 耦合性的解决方案。

JLRoutes

JLRoutes 是 github 上 Star 数目比较多的 URL 解析库,可以很方便的处理不同 URL Scheme 以及解析它们的参数,并通过回调 block 来处理 URL 对应的操作。我们可以通过定义 URL 的规则来定制我们的页面跳转或其他逻辑。例如假设我们需要在执行 ServiceMediator 类中的 start 方法,只需要

1 . 定义 URL,这里我们设置为

NSString *customURL = @"JLRoutesDemo://Service/ServiceMediator";

2 . 对我们注册的 URL 进行处理:

// 在 route 表中添加一条记录
[JLRoutes addRoute:@"/Service/:servicename" handler:^BOOL(NSDictionary<NSString *,NSString *> * _Nonnull parameters) {
 // 处理函数
 Class className = NSClassFromString(parameters[@"servicename"]);
 [className performSelector:@selector(startRoute)];
 return YES;
}];

具体代码大家可以看笔者的 Demo。

实现原理:

JLRoutes 在内部维护了一份 URL 字典,注册时添加元素,移除时删除元素。

MGJRouter

MGJRouter 是一个高效 / 灵活的 iOS URL Router, 解决了 JLRoutes 查找 URL 不够高效,通过遍历而不是匹配的问题。这里不多做介绍了, 大家可以自行 Google。

Objection

Objection 是一个轻量级的依赖注入框架。依赖注入对于客户端开发的我们可能不太熟悉,但服务端中使用很多,比如 Java 的 Spring 框架和 PHP 的 laravel 框架。

依赖注入的核心思想就是控制权反转 (Inversion of Control,IoC)。传统 iOS 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 Ioc 容器来控制对象的创建。具体到 Objective-C 中就是,先定义一个协议 (protocol),然后通过 objection 来注册这个协议对应的 class,需要的时候,可以获取该协议对应的 object。对于使用方无需关心到底使用的是哪个 Class,反正该有的方法、属性都有了 (在协议中指定):

// 先在 App 启动之前初始化容器
+(void)load
{
    JSObjectionInjector *injector = [JSObjection defaultInjector];
    injector = injector ? : [JSObjection createInjector];
    injector = [injector withModule:[[xxxModule alloc] init]];
    [JSObjection setDefaultInjector:injector];
}

xxxModule 就是我们需要绑定绑定 protocol 和具体实现类的地方, 假设我们有两个服务需要启动,可以如下处理:

//xxxModule.m 文件
[self bindClass:[NotificationService class] toProtocol:@protocol(NotificationServiceProtocol)];
[self bindClass:[ShareService class] toProtocol:@protocol(ShareServiceProtocol)];

接着我们只要通过如下代码获取这两个对象:

// 通知服务
JSObjectionInjector *injector = [JSObjection defaultInjector];
NSObject<NotificationServiceProtocol> *notificationService = [injector getObject:@protocol(NotificationServiceProtocol)];
// 分享服务
NSObject<ShareServiceProtocol> *shareService = [injector getObject:@protocol(ShareServiceProtocol)];

这样一来 notificationService 和 shareService 就被创建了,我们可以在这两个对象中编写我们的逻辑,省去了在 AppDelegate 中编写相应的代码,从而降低了耦合性。如果大家对这个库还有疑问,可以参考笔者的 Demo。

  总 结  

本文主要讲解了通过两种方式来瘦身 AppDelegate,虽然有所区别,但大致思路还是差不多的。希望对大家有所帮助。

本文 Demo 的地址:

https://github.com/zjh171/DelegateDietDemo

  参考
  • 豆瓣 App 的模块化实践

http://lincode.github.io/Modularity

  • AppDelegate 解耦之 JSDecoupledAppDelegate

https://zhongwuzw.github.io/2017/02/09/AppDelegate%E8%A7%A3%E8%80%A6%E4%B9%8BJSDecoupledAppDelegate/

  • 【源码阅读】JLRoutes

https://www.jianshu.com/p/55393770805a

  • 蘑菇街 App 的组件化之路

http://limboy.me/tech/2016/03/14/mgj-components-continued.html

  • dependency injection 关于 IOS 依赖注入那些事

https://www.jianshu.com/p/0d72a945f2dd

  • 使用 objection 来模块化开发 iOS 项目

http://limboy.me/tech/2014/04/15/use-objection-to-decouple-ios-project.html

 
前端之巅 更多文章 微信小程序将带来前端开发者的下一个黄金时代? 阿里开源GCanvas、G3D和BindingX:提升跨平台交互体验 88亿!Java侵权案大逆转,Google哭了,天地同悲 PWA登陆iOS了,但它还有这些缺陷 在AI横行的时代,你为什么还要固守大前端?
猜您喜欢 每日安全动态推送(02-27) 科学上网:Surge.conf for Geeks 设计模式总结 深入浅出ES6:魔力四射的生成器 Generators 续篇 C#内存管理机制及WP内存泄漏定位方法