AppDelegate代码组织

0x00 AppDelegate为什么变成了垃圾堆?

  当应用越做越大,功能越来越多,接入的第三方库越来越多,当开发人员越来越多的时候,AppDelegate就会变的越来越胖。

  那么下来接下来我们分析一下实际开发过程中我们都在AppDelegate里干什么?

  • 应用要处理一些URL(包括数据文件共享)
  • 接入推送通知
  • 处理3D Touch
  • 一些第三方SDK初始化
  • 其它数据操作处理…等等

假设我们的应用上面的功能都要实现,那么你可以想像一下AppDelegate将会变成什么样子。那我们要怎么样优雅的处理这些东西呢?

  • 2B程序猿:用到哪个功能就往AppDelegate生命周期对应的方法里塞相关代码
  • 普通程序猿:将要实现的功能抽离成单独函数在对应的生命周期方法里调用
  • 文艺程序猿:就是今天这篇博文所要讲述的Category大法

大家不要急,下面先简单的说一下Category&ClassExtension。

0x01 Category

Category的语法如下:

1
2
3
@interface AnyObject (FunctionName)
...
@end

一提到Category大家第一想到的是它能为已有的类扩展一些方法,但是另外一个作用往往会被大家所忽视,那就是优雅的组织代码。在类组织结构上,Category 可以用来帮助拆分功能,让一个大型的类按功能不同分治管理。

Category的在设计的层面上符合Decorator模式,如果我们把主类比作一个插板,那每个Category就相当于一个插件,可以随插随拔。

Tips: 在Category里扩展方法请在方法名前加前缀,这是一个很好的习惯,不要问我为什么…:

1
- (void)ft_dosomething:(id)obj;

0x02 Class Extension

Class Extension语法如下:

1
2
3
@interface AnyObject ()
...
@end

最初开始苹果爹并没有支持ClassExtension,如果要添加私有成员变量那是一个非常痛苦的体验。直到大概四年前的 WWDC 终于宣布添加上了 Class Extension 的语法,当时底下的开发者们含泪报以了热烈掌声,它让类的封装变的更加得心用手。

Tips: 在一个类里可以同时有N个Class Extension

0x03 Category 与 Class Extension的对比

Class Extension 和 Category 在语言机制上有着很大差别:Class Extension 在编译期就会将定义的 Ivar、属性、方法等直接合入主类,而 Category 在程序启动 Runtime Loading 时才会将属性(没 Ivar)和方法合入主类。Category不能直接添加成员变量,但是可以通过runtime里的objc association来实现,而Class Extension则可以直接添加成员变量

如果使用clang查看一个文件的抽象语法树(AST):

1
$ clang -Xclang -ast-dump -fsyntax-only main.m

我们可以发现Class Extension 和 Category 在 AST 中的表示都是 ObjCCategoryDecl,只是有无名字的区别,也可以说Class Extension 就是一个匿名的Category。

0x04 进入正题

铺垫,全都是铺垫,终于铺垫完了。

我以我自己参加环信的编程大赛做的一个应用(经过添油加醋的)来举例说明。
我们要在Appdelegate里在进行环信SDK的初始化,要实现3D Touch,要接入Facebook开源的一个内存检测工具,还要进行一些数据处理。

  • Step0:在工程中按功能创建分类,前在对应分类里实现对应的功能,如图:

  • Step1: 在AppDelegate.m导入对应功能的.h并在合适的位置调用,如图:

    大声告诉我,结过Category处理后的AppDelegate(没有经过Category处理前的场景自行脑补)是不是很瘦很苗条,逻辑清晰,简单易懂,童叟无欺。
    假如某个功能有问题,我们只需要到对应的功能分类里去Debug.
    假如现在我们想去掉某一个功能,我们只需要把#import “AppDelegate+xxxxx.h”删除,并删除对应功能的调用。是不是分分钟就去掉了。
    假如我们又需要这个功能,是不是又可以很容易的添加进来。

  • Step2: 本篇博文到此结束….其实还没有完
    下面我们看一下我是怎么实现Facebook内存检测工具分类的,因为它与其它几个分类稍有不同,先上图:

    大家有没有发现,上图中有两处红色框,一处的的方法是 ‘+’号的,你们在回头去看AppDelegate.m里发现我并没有调用到此方法。还有一处‘memeoryProfiler’这个变量是哪里来的呢?

    • 先来解释第一个红框,因为这段代码要在main.m返回应用代码前调用,看图:

      是不是很优雅,在main.m里只引用了这个分类,并没有引用到这个内存检测工具类的具体类库,假如我们要删除此功能是不是很简单的呢。还有你们有没有发现我这个内存检测工具只是在Debug下起作用,正式上线的时候你不会希望用户来使用它的。

    • 再来解释第二个红框,继续看图:

      这个变量我是在AppDelegate里声明的,上面也提到,Category并不能直接添加属性变量,虽然我们可以通过runtime来做到,但如果你回过头看上面这个属性的初始化时你会发现重写setter/getter方法不是很容易,所以我把这个属性声明在这个地方。

  • Step3: 这次真的结束了.
    最后送大家一个胖AppDelegate或者说在AppDelegate里做了一些不合适的操作会引起的崩溃(Watch Dog 机制):

    1
    Exception Type: 00000020 Exception Codes: 0x8badf00d

0x8badf00d: 读做 “ate bad food”! (把数字换成字母,是不是很像 😼)该编码表示应用是因为发生watchdog超时而被iOS终止的。 通常是应用花费太多时间而无法启动、终止或响应用系统事件。

从iOS4.x开始,退出应用时,应用不会立即终止,而是退到后台。但是,如果你的应用响应不够快,操作系统有可能会终止你的应用,并产生一个崩溃日志。这些事件与下列UIApplicationDelegate方法相对应:

  • application:didFinishLaunchingWithOptions:
  • applicationWillResignActive:
  • applicationDidEnterBackground:
  • applicationWillEnterForeground:
  • applicationDidBecomeActive:
  • applicationWillTerminate:

上面所有这些方法,应用只有有限的时间去完成处理。如果花费时间太长,操作系统将终止应用.你的崩溃日志里将会有以下一段异常类型和异常代码:

当遇到这种崩溃时你要做的是查看崩溃日志,追踪是哪些代码阻塞了主线程做了耗时操作,并将其移到子线程中去。

希望看完后会对你有所帮助,可以在其它类里举一反三。

本文部分参考sunnyxx的博客里的这篇文章

http://blog.sunnyxx.com/2016/04/22/objc-class-extension-tips/#rd