AppDelegate代码组织
0x00 AppDelegate为什么变成了垃圾堆?
当应用越做越大,功能越来越多,接入的第三方库越来越多,当开发人员越来越多的时候,AppDelegate就会变的越来越胖。
那么下来接下来我们分析一下实际开发过程中我们都在AppDelegate里干什么?
- 应用要处理一些URL(包括数据文件共享)
- 接入推送通知
- 处理3D Touch
- 一些第三方SDK初始化
- 其它数据操作处理…等等
假设我们的应用上面的功能都要实现,那么你可以想像一下AppDelegate将会变成什么样子。那我们要怎么样优雅的处理这些东西呢?
- 2B程序猿:用到哪个功能就往AppDelegate生命周期对应的方法里塞相关代码
- 普通程序猿:将要实现的功能抽离成单独函数在对应的生命周期方法里调用
- 文艺程序猿:就是今天这篇博文所要讲述的Category大法
大家不要急,下面先简单的说一下Category&ClassExtension。
0x01 Category
Category的语法如下:
1 | @interface AnyObject (FunctionName) |
一提到Category大家第一想到的是它能为已有的类扩展一些方法,但是另外一个作用往往会被大家所忽视,那就是优雅的组织代码。在类组织结构上,Category 可以用来帮助拆分功能,让一个大型的类按功能不同分治管理。
Category的在设计的层面上符合Decorator模式,如果我们把主类比作一个插板,那每个Category就相当于一个插件,可以随插随拔。
Tips: 在Category里扩展方法请在方法名前加前缀,这是一个很好的习惯,不要问我为什么…:
1 | - (void)ft_dosomething:(id)obj; |
0x02 Class Extension
Class Extension语法如下:
1 | @interface AnyObject () |
最初开始苹果爹并没有支持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