iOS 重构AppDelegate
一、Massive AppDelegate
AppDelegate 是应用程序的根对象,它连接应用程序和系统,确保应用程序与系统以及其他应用程序正确的交互,通常被认为是每个 iOS 项目的核心。
随着开发的迭代升级,不断增加新的功能和业务,它的代码量也不断增长,最终导致了 Massive AppDelegate。
在复杂 AppDelegate 里修改任何东西的成本都是很高的,因为它将会影响你的整个 APP,一不留神产生 bug。毫无疑问,保持 AppDelegate 的简洁和清晰对于健康的 iOS 架构来说是至关重要的。本文将使用多种方法来重构,使之简洁、可重用和可测。
AppDelegate 常见的业务代码如下:
- 日志埋点统计数据分析
- 初始化数据存储系统
- 配置 UIAppearance
- 管理 App Badge 数字
- 管理通知:请求权限,存储令牌,处理自定义操作,将通知传播到应用程序的其余部分
- 管理 UI 堆栈配置:选择初始视图控制器,执行根视图控制器转换
- 管理 UserDefaults:设置首先启动标志,保存和加载数据
- 管理后台任务
- 管理设备方向
- 更新位置信息
- 初始化第三方库(如分享、日志、第三方登陆、支付)
这些臃肿的代码是反模式的,导致难于维护,显然支持扩展和测试这样的类非常复杂且容易出错。Massive AppDelegates 与我们经常谈的 Massive ViewController 的症状非常类似。
看看以下可能的解决方案,每个 Recipe(方案)遵循单一职责、易于扩展、易于测试原则。
二、命令模式 Command Design Pattern
命令模式是一种数据驱动的设计模式,属于行为型模式。
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。因此命令的调用者无需关心命令做了什么以及响应者是谁。
可以为 AppDelegate 的每一个职责定义一个命令,这个命令的名字自行指定。
/// 命令协议
@protocol Command <NSObject>
- (void)execute;
@end
/// 初始化第三方库
@interface InitializeThirdPartiesCommand : NSObject <Command>
@end
/// 初始化主视图
@interface InitializeRootViewControllerCommand : NSObject <Command>
@property (nonatomic, strong) UIWindow * keyWindow;
@end
/// 初始化视图全局配置
@interface InitializeAppearanceCommand : NSObject <Command>
@end
/// ...
然后定义一个统一调用的类 StartupCommandsBuilder 来封装如何创建命令的详细信息。AppDelegate 调用这个 builder 去初始化命令并执行这些命令。
@implementation StartupCommandsBuilder
// 返回数组,元素为遵守 Command 协议的对象
- (NSArray<id<Command>> *)build
{
return @[ [InitializeAppearanceCommand new],
[InitializeRootViewControllerCommand new],
[InitializeThirdPartiesCommand new] ];
}
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[[[StartupCommandsBuilder alloc] init] build] enumerateObjectsUsingBlock:^(id<Command> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj execute];
}];
return YES;
}
如果 AppDelegate 需要添加新的职责,则可以创建新的命令,然后把命令添加到 Builder 里而无需去改变 AppDelegate。解决方案满足单一职责、易于扩展、易于测试原则。
三、组合设计模式 Composite Design Pattern
组合模式又叫部分整体模式,用于把一组相似的对象当作一个单一的对象。
组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。一个很明显的例子就是 iOS 里的 UIView 以及它的 subviews。
这个想法主要是有一个组装类和叶子类,每个叶子类负责一个职责,而组装类负责调用所有叶子类的方法。
/// 组装类
@interface CompositeAppDelegate : UIResponder <UIApplicationDelegate>
+ (instancetype)makeDefault;
@end
@implementation CompositeAppDelegate
+ (instancetype)makeDefault
{
// 这里要实现单例
return [[CompositeAppDelegate alloc] init];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[PushNotificationAppDelegate new] application:application didFinishLaunchingWithOptions:launchOptions];
[[ThirdPartiesConfiguratorAppDelegate new] application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
@end
实现执行具体职责的叶子类。
/// 叶子类。推送消息处理
@interface PushNotificationAppDelegate : UIResponder <UIApplicationDelegate>
@end
/// 叶子类。初始化第三方库
@interface ThirdPartiesConfiguratorAppDelegate : UIResponder <UIApplicationDelegate>
@end
@implementation PushNotificationAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(@"PushNotificationAppDelegate");
return YES;
}
@end
@implementation ThirdPartiesConfiguratorAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(@"ThirdPartiesConfiguratorAppDelegate");
return YES;
}
@end
在 AppDelegate 通过工厂方法创建组装类,然后通过它去调用所有的方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[CompositeAppDelegate makeDefault] application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
它满足我们在开始时提出的所有要求,如果要添加一个新的功能,很容易添加一个叶子类,无需改变 AppDelegate,解决方案满足单一职责、易于扩展、易于测试原则。
四、中介者模式 Mediator Design Pattern
中介者模式是用来降低多个对象和类之间的通信复杂性。
这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
如果想了解有关此模式的更多信息,建议查看 Mediator Pattern Case Study。或者阅读文末给出关于设计模式比较经典的书籍。
让我们定义 AppLifecycleMediator 将 UIApplication 的生命周期通知底下的监听者,这些监听者必须遵循AppLifecycleListener 协议,如果需要监听者要能扩展新的方法。
@interface APPLifeCycleMediator : NSObject
+ (instancetype)makeDefaultMediator;
@end
@implementation APPLifeCycleMediator
{
@private
NSArray<id<AppLifeCycleListener>> * _listeners;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)initWithListeners:(NSArray<id<AppLifeCycleListener>> *)listeners
{
if (self = [super init]) {
_listeners = listeners;
// 通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onAppWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onAppDidEnterBackgroud)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onAppDidFinishLaunching)
name:UIApplicationDidFinishLaunchingNotification
object:nil];
}
return self;
}
/// 定义好静态类方法,初始化所有监听者
+ (instancetype)makeDefaultMediator
{
static APPLifeCycleMediator * mediator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mediator = [[APPLifeCycleMediator alloc] initWithListeners:@[[VideoListener new], [SocketListener new]]];
});
return mediator;
}
- (void)onAppWillEnterForeground
{
[_listeners[1] onAppWillEnterForeground];
}
- (void)onAppDidEnterBackgroud
{
[_listeners[0] onAppDidEnterBackgroud];
}
- (void)onAppDidFinishLaunching
{
}
@end
定义 AppLifecycleListener 协议,以及协议的的实现者。
/// 监听协议
@protocol AppLifeCycleListener <NSObject>
@optional
- (void)onAppWillEnterForeground;
- (void)onAppDidEnterBackgroud;
- (void)onAppDidFinishLaunching;
@end
@interface VideoListener : NSObject <AppLifeCycleListener>
@end
@interface SocketListener : NSObject <AppLifeCycleListener>
@end
@implementation VideoListener
- (void)onAppDidEnterBackgroud
{
NSLog(@"停止视频播放");
}
@end
@implementation SocketListener
- (void)onAppWillEnterForeground
{
NSLog(@"开启长链接");
}
@end
加入到 AppDelegate 中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[APPLifeCycleMediator makeDefaultMediator];
return YES;
}
这个中介者自动订阅了所有的事件。AppDelegate 仅仅需要初始化它一次,就能让它正常工作。每个监听者都有一个单一职责,很容易添加一个监听者,而无需改变 Appdelgate 的内容,每个监听者以及中介者能够容易的被单独测试。
五、总结
大多数 AppDelegates 的设计都不太合理,过于复杂并且职责过多。我们称这样的类为 Massive App Delegates。
通过应用软件设计模式,Massive App Delegate 可以分成几个单独的类,每个类都有单一的责任,可以单独测试。
这样的代码很容易更改维护,因为它不会在您的应用程序中产生一连串的更改。它非常灵活,可以在将来提取和重用。
六、学习文章
Refactoring Massive App Delegate
OC设计模式:《Objective-C 编程之道:iOS 设计模式解析》
Swift 设计模式:《Design_Patterns_by_Tutorials_v0.9.1》
重构:《重构改善既有代码的设计》
iOS 重构AppDelegate的更多相关文章
- [转] IOS中AppDelegate中的生命周期事件的调用条件
IOS中AppDelegate中的生命周期事件的调用条件 //当应用程序将要进入非活动状态执行,在此期间,应用程序不接受消息或事件,比如来电 - (void)applicationWillResign ...
- [ios]IOS的AppDelegate方法中的事件触发调用 以及 关闭 ios应用程序
IOS的AppDelegate方法中的事件触发调用 参考:http://blog.sina.com.cn/s/blog_a573f7990101bphp.html //当应用程序将要进入非活动状态执行 ...
- iOS重构项目之路
iOS重构项目之路 1.整理目录 按照功能模块对整个工程的目录进行分类,比如 2.整理资源文件 删除多余的图片文件,资源文件 图片资源尽量添加到Assets.xcassets中 删除项目中未引用的图片 ...
- 【iOS发展-44】通过案例谈iOS重构:合并、格式化输出、宏观变量、使用数组来存储数据字典,而且使用plist最终的知识
我们今天的情况下是第一个例子,下面的5一来通过切换页上一页下一页: (1)第一步,基本是以非常傻非常直接的方式来创建.这里用到的主要点有: --把对象变量设置为全局变量使得能够在其它方法中调用来设置它 ...
- 用c#开发苹果应用程序 xamarin.ios方式
NetworkComms网络通信框架序言 Networkcomms网络通信框架来自于英国,支持以xamarin.ios的方式开发苹果应用程序 其开源版本2.3.1中带有一个示例程序,实现聊天功能,只要 ...
- iOS完整学习路线图-对知识的回顾/整理
第一阶段:语言基础 Mac系统使用.常用UNIX指令.C语言.Objective-C语言.Foundation框架. 第二阶段:iOS基础 AppDelegate & UIApplicatio ...
- Xcode 11新建工程.--iOS 13 SceneDelegate适配
收录文章::::::::::::::: iOS 13 适配要点总结 在Xcode 11 创建的工程,运行设备选择 iOS 13.0 以下的设备,运行应用时会出现黑屏现象.原因: Xcode 11 默认 ...
- Xamarin.Forms跨平台开发入门-第二部分:深入解析
英文原文: https://developer.xamarin.com/guides/xamarin-forms/getting-started/hello-xamarin-forms/deepdiv ...
- React Native MAC上环境搭建笔记
今天花了一点时间搭建了一下react native环境,在这个过程中遇到了一些问题,处理并总结一下,年纪大了记性不好,只能多写写...真是岁月不饶人啊! 第一步:安装最新版本的Xcode工具 第二步: ...
随机推荐
- Java基础--冒泡排序算法
冒泡排序算法的运作如下:(从后往前) 比较相邻的元素,如果第一个比第二个大,就交换他们两个. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的元素. 针对 ...
- 基于springcloud框架搭建项目-Eureka篇(一)
springcloud项目近年来算是很流行的了,不少公司项目目前都用到了,毕竟优点很多,刚好公司项目用到了,根据自己的理解,简单搭建一下,以便以后学习 这里简单的介绍一下它: SpringCloud, ...
- 对javaweb项目中web.xml重用配置的理解(个人学习小结)
<!-- 所有的总结描述性与语言都在注释中 --><?xml version="1.0" encoding="UTF-8"?> < ...
- Python进阶练习与爬取豆瓣T250的影片相关信息
(一)Python进阶练习 正所谓要将知识进行实践,才会真正的掌握 于是就练习了几道题:求素数,求奇数,求九九乘法表,字符串练习 import re #求素数 i=1; flag=0 while(i& ...
- 判断 tableZen 是否有 横向滚动条
判断 tableZen 是否有 横向滚动条 const outWidth = this.$refs.tableInnerZen.$el.clientWidth ].$el.clientWidth
- WebRTC的RTCPeerConnection()原理探析
从getUserMedia()到RTCPeerConnection(),自认为难度陡增.我想一方面是之前在Linux平台上学习ROS调用摄像头时,对底层的外设接口调用.摄像头参数都有学习理解:另一方面 ...
- 《前端之路》- TypeScript (三) ES5 中实现继承、类以及原理
目录 一.先讲讲 ES5 中构造函数(类)静态方法和多态 1-1 JS 中原型以及原型链 例子一 1-2 JS 中原型以及原型链中,我们常见的 constructor.prototype.**prot ...
- 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)
要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...
- Spring Boot框架——快速入门
Spring Boot是Spring 全家桶非常重要的一个模块,通过 Spring Boot 可以快速搭建一个基于 Spring 的 Java 应用程序,Spring Boot 对常用的第三方库提供了 ...
- 2020年Java基础高频面试题汇总(1.4W字详细解析)
1. Java语言有哪些特点 (1)简单易学.有丰富的类库 (2)面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高) (3)与平台无关性(JVM是Java跨平台使用的根本) (4)可靠安全 ...