AppDelegate瘦身 之 AMKApplicationDelegate
本文原文地址:https://www.jianshu.com/p/666cbd2b7ec8 代码地址:https://github.com/AndyM129/AMKApplicationDelegate
背景
在 iOS 项目的开发中,AppDelegate 是一个容易引发耦合的地方,很多项目随着开发时间的增长,AppDelegate 就不可避免地变得庞大,代码变得复杂,调用顺序混乱。
因此,可以通过采用 AMKApplicationDelegate
无侵入地实现 AppDelegate 的瘦身。
AppDelegate瘦身 之 AMKApplicationDelegate
“AppDelegate 瘦身” 这个话题确实被反复提起,之所以再次讨论,是因为我在浏览博客时,偶尔看到这样一段精辟的观点:
阅读这段深刻的见解后,我真是感慨万千,为什么自己就没有想过呢 —— 在参与的多个项目中,每个项目的 AppDelegate
似乎总是越来越庞大,即便通过分类做了某些优化,效果也不是很理想:
- 在后期维护时,一段代码放在哪里更合理,需要研发人员具备一定的个人修养才能做出判断,稍不留神,原本所做的优化又会被打乱。
- 相同方法的实现会在分类之间相互覆盖,因此只能通过使用 别名(例如加前缀)的方式实现,再统一调用,但这最终会导致主类中引入大量的分类和方法调用逻辑。
思考
在理解了上述思路后,我没有盲目地进行编码,而是打开 GitHub,输入 AppDelegate
,搜索看看有哪些现有的好的实现。
其中,《DelegateDietDemo - AppDelegate瘦身指南Demo》 (另可见原文)对目前的常见方案做了一个梳理,但我觉得这些都不够好:
因此,我考虑了另外一种方案,更准确地说,是几种方案的结合:
- 将之前的每个 分类 都改为一个独立的 代理类,以处理该模块中 App 在各个生命周期的事项。
- 用一个管理类替换原来的
AppDelegate
,使这个管理类能够将生命周期的各个方法分配给对应的代理类。 - 在若干个代理类中标记一个主代理,进行统筹安排,或进行
UIWindow
实例化等操作。 - 在
main.m
文件中,改用代理类。
int main(int argc, char * argv[]) {
@autoreleasepool {
// 之前的用法
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
// 改用 管理类
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AMKApplicationDelegate class]));
}
}
以下是新方案与目前现有开源解决方案的优劣势对比:
核心代码
注意:该项目在 Xcode Version 9.2 上开发,目标支持iOS8+,尚未测试更早的iOS版本。
AMKApplicationDelegate.h
//
// AMKApplicationDelegate.h
// AMKApplicationDelegate
//
// Created by Meng,Xinxin on 2018/5/3.
//
#import <UIKit/UIKit.h>
/** ApplicationDelegate 解耦 */
@interface AMKApplicationDelegate : UIResponder <UIApplicationDelegate> {
@protected NSArray<id<UIApplicationDelegate>> *_applicationDelegates;
}
@property(nonatomic, strong, readonly, class) AMKApplicationDelegate *sharedInstance;
@property(nonatomic, strong, readonly) NSArray<id<UIApplicationDelegate>> *applicationDelegates;
@property(nonatomic, strong, readonly) UIResponder<UIApplicationDelegate> *mainApplicationDelegate;
@end
/** 主ApplicationDelegate */
@protocol AMKMainApplicationDelegate <UIApplicationDelegate> @end
AMKApplicationDelegate.m
#import "AMKApplicationDelegate.h"
@interface AMKApplicationDelegate () @end
@implementation AMKApplicationDelegate
#pragma mark -- Properties --
@synthesize applicationDelegates = _applicationDelegates;
- (UIWindow *)window {
return self.mainApplicationDelegate.window;
}
#pragma mark -- Public Methods --
+ (AMKApplicationDelegate *)sharedInstance {
static AMKApplicationDelegate *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (UIResponder<UIApplicationDelegate> *)mainApplicationDelegate {
static UIResponder<UIApplicationDelegate> *mainApplicationDelegate = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 查找主代理
for (UIResponder<UIApplicationDelegate> *applicationDelegate in self.applicationDelegates) {
if ([applicationDelegate conformsToProtocol:@protocol(AMKMainApplicationDelegate)] && [applicationDelegate isKindOfClass:UIResponder.class]) {
// 断言
NSAssert(mainApplicationDelegate==nil, @"`AMKMainApplicationDelegate` 协议的实现类有且仅有一个");
// 赋值主代理
mainApplicationDelegate = applicationDelegate;
}
}
// 主代理有效性判断
NSAssert(mainApplicationDelegate!=nil, @"`AMKMainApplicationDelegate` 协议的实现类有且仅有一个, 且为`UIResponder`子类");
});
return mainApplicationDelegate;
}
@end
...
@implementation AMKApplicationDelegate (UIApplicationDelegate)
...
// 当应用程序启动完毕的时候就会调用(系统自动调用)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
BOOL flag = YES;
for (id<UIApplicationDelegate> applicationDelegate in self.applicationDelegates) {
if (![applicationDelegate respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) continue;
flag = flag && [applicationDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
return flag;
}
...
@end
使用
1. 引入
AMKApplicationDelegate
可以通过CocoaPods引入,仅需在工程的Podfile
文件中添加以下代码:
pod 'AMKApplicationDelegate'
然后在终端在Podfile
文件所在路径下执行pod install
命令即可完成源码下载与引入。
2. 接入
(1) 改写默认 AppDelegate
- 改写
main.m
文件
@import UIKit;
#import "AMKAppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass(AMKApplicationDelegate.class));
}
}
(2) 注册 主AppDelegate
- 创建
AMKApplicationDelegate
的分类
// .h
#import <AMKApplicationDelegate/AMKApplicationDelegate.h>
@interface AMKApplicationDelegate (Demo) @end
// .m
#import "AMKApplicationDelegate+Demo.h"
#import "AMKAppDelegate.h"
@implementation AMKApplicationDelegate (Demo)
- (instancetype)init {
if (self = [super init]) {
NSMutableArray *applicationDelegates = [NSMutableArray array];
[applicationDelegates addObject:AMKAppDelegate.new];
self->_applicationDelegates = applicationDelegates;
}
return self;
}
@end
- 在
AMKAppDelegate.h
文件中实现AMKMainApplicationDelegate
协议
#import "AMKApplicationDelegate.h"
@interface AMKAppDelegate : UIResponder <UIApplicationDelegate, AMKMainApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
注:
AMKApplicationDelegate
本身没有干预App生命周期方法中的各代理类调用顺序,直接以初始化的顺序调用,使用者可以按需初始化~
3. 开发
以下以“添加 3D-Touch快捷方式”为例,介绍如何横向扩展 AppDelegate。
(1) 创建管理类
AMKApplicationShortcutManager.h
#import <Foundation/Foundation.h>
/// 快捷方式
@interface AMKApplicationShortcutManager : NSObject <UIApplicationDelegate>
@end
AMKApplicationShortcutManager.m
#import "AMKApplicationShortcutManager.h"
NSString * const AMKApplicationShortcutItemTitleUserInfoKey = @"title";
NSString * const AMKApplicationShortcutItemMessageUserInfoKey = @"message";
@implementation AMKApplicationShortcutManager
/** 快捷入口 */
- (void)setupShortcutItems {
if ([[UIApplication sharedApplication] respondsToSelector:@selector(shortcutItems)]) {
NSMutableArray *shortcutItems = [NSMutableArray array];
[shortcutItems addObject:({
NSString *type = @"shortcutItem1";
NSString *title = @"签到";
NSString *subtitle = @"我是快捷操作描述";
NSString *message = [NSString stringWithFormat:@"您点击了“%@”的快捷方式", title];
NSMutableDictionary *userInfo = @{}.mutableCopy;
userInfo[AMKApplicationShortcutItemTitleUserInfoKey] = title;
userInfo[AMKApplicationShortcutItemMessageUserInfoKey] = message;
UIApplicationShortcutItem *shortcutItem = [[UIApplicationShortcutItem alloc] initWithType:type localizedTitle:title localizedSubtitle:subtitle icon:[UIApplicationShortcutIcon iconWithTemplateImageName:@""] userInfo:userInfo];
shortcutItem;
})];
[shortcutItems addObject:({
NSString *type = @"shortcutItem2";
NSString *title = @"查找";
NSString *subtitle = @"我是快捷操作描述";
NSString *message = [NSString stringWithFormat:@"您点击了“%@”的快捷方式", title];
NSMutableDictionary *userInfo = @{}.mutableCopy;
userInfo[AMKApplicationShortcutItemTitleUserInfoKey] = title;
userInfo[AMKApplicationShortcutItemMessageUserInfoKey] = message;
UIApplicationShortcutItem *shortcutItem = [[UIApplicationShortcutItem alloc] initWithType:type localizedTitle:title localizedSubtitle:subtitle icon:[UIApplicationShortcutIcon iconWithTemplateImageName:@""] userInfo:userInfo];
shortcutItem;
})];
[UIApplication sharedApplication].shortcutItems = shortcutItems;
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
[self setupShortcutItems];
return YES;
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
NSString *title = [shortcutItem.userInfo objectForKey:AMKApplicationShortcutItemTitleUserInfoKey];
NSString *message = [shortcutItem.userInfo objectForKey:AMKApplicationShortcutItemMessageUserInfoKey];
[[[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
}
@end
(2) 注册
- 在
AMKApplicationDelegate+Demo.m
分类文件中注册新创建的管理类
#import "AMKApplicationDelegate+Demo.h"
#import "AMKApplicationShortcutManager.h"
@implementation AMKApplicationDelegate (Demo)
- (instancetype)init {
if (self = [super init]) {
NSMutableArray *applicationDelegates = [NSMutableArray array];
[applicationDelegates addObject:AMKAppDelegate.new];
[applicationDelegates addObject:AMKApplicationShortcutManager.new]; // 注册 3D-Touch快捷方式
self->_applicationDelegates = applicationDelegates;
}
return self;
}
@end
执行结果
图片可能太大,如果加载失败,请访问以下地址查看: https://github.com/AndyM129/AMKApplicationDelegate/blob/master/demo.gif
后话
本文原文地址:https://www.jianshu.com/p/666cbd2b7ec8 代码地址:https://github.com/AndyM129/AMKApplicationDelegate
如果您有好的想法或疑问,请随时提出 issue 或 request。
如果您在开发过程中遇到任何问题,或对iOS开发有自己的独特见解,或与您一样是新手,都可以关注或私信我的微博。
- 微信:Andy_129
- 微博:@Developer_Andy
- 简书:Andy__M
“Stay hungry. Stay foolish.”
共勉~