简介
在iOS工程中,AppDelegate往往会有上千行,甚至几千行,这样就会给维护AppDelegate带来诸多麻烦。比方说,老板想在出现HomeViewController之前弹出广告并停顿几秒,这样你就要加入插入广告的逻辑;又比方说,老板想在开始做个请求,判断某个开关是否打开。这样就会在AppDelegate中插入很多相关的不相关的代码。
在AppDelegate中,- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions是“Tells the delegate when the application has launched and may have additional launch options to handle.”,即在app开始运行时会调用里面的方法。在didFinishLaunchingWithOptions中,我们往往会渲染window,注册第三方监控库,加入基本页面跳转逻辑。
下面是一个常见项目中的didFinishLaunchingWithOptions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
// objective-c语言
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if
(!([ADeanUserDataManager sharedManager].userName != nil &&
[ADeanUserDataManager sharedManager].userName.length > 0 &&
[ADeanUserDataManager sharedManager].userPassword != nil &&
[ADeanUserDataManager sharedManager].userPassword.length > 0)) {
// 用户名、密码为空时候强制为未登录
[ADeanUserDataManager sharedManager].isUserLogined = @NO;
}
self.window.rootViewController = self.tabbarController;
[self.window makeKeyAndVisible];
// 基本页面跳转逻辑
/*--------------------------------------*/
if
([[ADeanUserDataManager sharedManager].everLaunched boolValue] == NO) {
//是否是第一次启动判断
[ADeanUserDataManager sharedManager].everLaunched = [NSNumber numberWithBool:YES];
[self.window addSubview:self.helpViewController.view];
}
/*--------------------------------------*/
// 注册第三方库
/*--------------------------------------*/
// 注册Crash统计 -- Crashlytics
[Fabric
with
:@[[Crashlytics class]]];
[MobClick startWithAppkey:UMENG_APPKEY];
[MobClick setCrashReportEnabled:NO];
// 关掉MobClick Crash Report收集开关
#ifdef ADeanForTest
[MobClick setCrashReportEnabled:YES];
// 打开MobClick Crash Report收集开关
[MobClick setLogEnabled:YES];
#endif
[ShareSDK registerApp:ShareSDKAppKey];
//新浪
[ShareSDK connectSinaWeiboWithAppKey:SinaAppKey
appSecret:SinaAppSecret
redirectUri:SinaRedirectUri];
//新浪微博客户端应用
[ShareSDK connectSinaWeiboWithAppKey:SinaAppKey
appSecret:SinaAppSecret
redirectUri:SinaRedirectUri
weiboSDKCls:[WeiboSDK class]];
#if TARGET_IPHONE_SIMULATOR
#else
//QQ好友
[ShareSDK connectQQWithQZoneAppKey:QZoneAppKey
qqApiInterfaceCls:[QQApiInterface class]
tencentOAuthCls:[TencentOAuth class]];
#endif
//微信朋友圈
[ShareSDK connectWeChatSessionWithAppId:WeiXinAppID wechatCls:[WXApi class]];
//微信好友
[ShareSDK connectWeChatTimelineWithAppId:WeiXinAppID wechatCls:[WXApi class]];
[MiPushSDK registerMiPush:self type:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert) connect:YES];
/*--------------------------------------*/
// 其他逻辑
[self registerRemotePushNotification];
[self getSwitchInfoFromService];
[self appIntegrityCheck];
[self appSecurityCheck]
......
return
YES;
}
|
下面我们就来看看,有什么好的办法可以对AppDelegate进行瘦身,加强代码的可读性和可维护性,并将代码放到适当的地方。
函数模块化
上述didFinishLaunchingWithOptions中可以按照功能逻辑划分为:处理启动逻辑,注册第三方库,处理其他逻辑三类。这样就可以优化为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
// objective-c语言
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if
(!([ADeanUserDataManager sharedManager].userName != nil &&
[ADeanUserDataManager sharedManager].userName.length > 0 &&
[ADeanUserDataManager sharedManager].userPassword != nil &&
[ADeanUserDataManager sharedManager].userPassword.length > 0)) {
// 用户名、密码为空时候强制为未登录
[ADeanUserDataManager sharedManager].isUserLogined = @NO;
}
self.window.rootViewController = self.tabbarController;
[self.window makeKeyAndVisible];
// 基本页面跳转逻辑
[self baseViewJumpLogic];
// 注册第三方库
[self registThirdPart];
// 其他逻辑
[self handleOtherLogic]
return
YES;
}
- (void)baseViewJumpLogic {
if
([[ADeanUserDataManager sharedManager].everLaunched boolValue] == NO) {
//是否是第一次启动判断
[ADeanUserDataManager sharedManager].everLaunched = [NSNumber numberWithBool:YES];
[self.window addSubview:self.helpViewController.view];
}
}
- (void)registThirdPart {
// 注册Crash统计 -- Crashlytics
[Fabric
with
:@[[Crashlytics class]]];
[MobClick startWithAppkey:UMENG_APPKEY];
[MobClick setCrashReportEnabled:NO];
// 关掉MobClick Crash Report收集开关
#ifdef ADeanForTest
[MobClick setCrashReportEnabled:YES];
// 打开MobClick Crash Report收集开关
[MobClick setLogEnabled:YES];
#endif
[ShareSDK registerApp:ShareSDKAppKey];
//新浪
[ShareSDK connectSinaWeiboWithAppKey:SinaAppKey
appSecret:SinaAppSecret
redirectUri:SinaRedirectUri];
//新浪微博客户端应用
[ShareSDK connectSinaWeiboWithAppKey:SinaAppKey
appSecret:SinaAppSecret
redirectUri:SinaRedirectUri
weiboSDKCls:[WeiboSDK class]];
#if TARGET_IPHONE_SIMULATOR
#else
//QQ好友
[ShareSDK connectQQWithQZoneAppKey:QZoneAppKey
qqApiInterfaceCls:[QQApiInterface class]
tencentOAuthCls:[TencentOAuth class]];
#endif
//微信朋友圈
[ShareSDK connectWeChatSessionWithAppId:WeiXinAppID wechatCls:[WXApi class]];
//微信好友
[ShareSDK connectWeChatTimelineWithAppId:WeiXinAppID wechatCls:[WXApi class]];
[MiPushSDK registerMiPush:self type:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert) connect:YES];
}
- (void)handleOtherLogic {
[self registerRemotePushNotification];
[self getSwitchInfoFromService];
[self appIntegrityCheck];
[self appSecurityCheck]
......
}
|
模块化后,代码瞬间变得易读很多,而且需要改什么可以直接去相应的模块添加。但是这个仅仅是将代码的顺序变化下,相同功能的代码抽到一个函数中,代码行数没有减少,所有的功能还是糅合在一个.m中。
类模块化
很多其他逻辑是业务逻辑的,可以抽离到业务Model中,通过类模块化便捷使用。这样就可以优化为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// objective-c语言
#import "ADeanAppCheck.h"
#import "ADeanSwitches.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if
(!([ADeanUserDataManager sharedManager].userName != nil &&
[ADeanUserDataManager sharedManager].userName.length > 0 &&
[ADeanUserDataManager sharedManager].userPassword != nil &&
[ADeanUserDataManager sharedManager].userPassword.length > 0)) {
// 用户名、密码为空时候强制为未登录
[ADeanUserDataManager sharedManager].isUserLogined = @NO;
}
self.window.rootViewController = self.tabbarController;
[self.window makeKeyAndVisible];
// 基本页面跳转逻辑
[self baseViewJumpLogic];
// 注册第三方库
[self registThirdPart];
// 其他逻辑
[self handleOtherLogic]
return
YES;
}
- (void)handleOtherLogic {
[ADeanAppCheck appInfoCheck];
// Integrity & Security Check
[ADeanSwitches appSwitchInit];
// Get Switch From Service
......
}
|
分类模块化
先抛个问题:分类中是否可以定义变量?
如果不知道可以参考:iOS分类中通过runtime添加动态属性
分类能够做到的事情主要是:即使在你不知道一个类的源码情况下,向这个类添加扩展的方法。这里我们主要是将对外开放的方法和一部分变量拿到分类中处理。这样进一步轻量化AppDelegate本身进行代码量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// objective-c语言
// ADeanAppDelegate+Light.h文件
#import "AppDelegate.h"
@interface ADeanAppDelegate (Light)
@property (nonatomic, strong) UITabbarController *tabbarController;
/*!
@brief 全局appDeleaget
*/
+ (AppDelegate *)appDelegate;
/*!
@method
@brief 关闭系统键盘
*/
+ (void)closeKeyWindow;
@end
// objective-c语言
// ADeanAppDelegate+Light.m文件
#import "ADeanAppDelegate+Light.h"
- (UITabbarController *)tabbarController {
UITabbarController *tabbarController = objc_getAssociatedObject(self, &kTabbarControllerObjectKey);
if
(!tabbarController) {
tabbarController = [[UITabbarController alloc] init];
objc_setAssociatedObject(self, &kTabbarControllerObjectKey, tabbarController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return
tabbarController;
}
- (void)setTabbarController:(UITabbarController *)tabbarController {
objc_setAssociatedObject(self, &kTabbarControllerObjectKey, tabbarController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (AppDelegate *)appDelegate {
return
(AppDelegate *)[[UIApplication sharedApplication] delegate];
}
+ (void)closeKeyWindow {
[[UIApplication sharedApplication].keyWindow endEditing:YES];
}
这样在AppDelegate中,对外开放的方法和部分变量可以抽离到分类中去。也可以根据作用定义不同的AppDelegate分类:
#“ADeanAppDelegate+View.h”
#“ADeanAppDelegate+Controller.h”
#“ADeanAppDelegate+Method.h”
…
|
这样代码结构会更加清晰明了。 抽出来的AppDelegate只剩下注册第三方库了,因为第三方库很多是需要在didFinishLaunchingWithOptions中运行,正常的方法就很难。
Method Swizzling化
Method Swizzling是改变一个selector的实际实现的技术,关于Method Swizzling的概念、原理谷歌一堆。
Method Swizzling中以viewWillAppear为例,讲解了Method Swizzling的基本用法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
#import "ADeanAppDelegate+Hook.h"
#import "ADeanMethodSwizzling.h"
#import "MobClick.h"
#import "WXApi.h"
#import "WeiboSDK.h"
#import
#import
#import
#if TARGET_IPHONE_SIMULATOR
#else
#import
#import
#import
#endif
@implementation ADeanAppDelegate (Hook)
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self adean_AppDelegateHook];
});
}
+ (void)adean_AppDelegateHook
{
SwizzlingMethod([ADeanAppDelegate class], @selector(application:didFinishLaunchingWithOptions:), @selector(adean_application:didFinishLaunchingWithOptions:));
SwizzlingMethod([ADeanAppDelegate class], @selector(application:handleOpenURL:), @selector(adean_application:handleOpenURL:));
SwizzlingMethod([ADeanAppDelegate class], @selector(application:openURL:sourceApplication:annotation:), @selector(adean_application:openURL:sourceApplication:annotation:));
SwizzlingMethod([ADeanAppDelegate class], @selector(applicationDidReceiveMemoryWarning:), @selector(adean_applicationDidReceiveMemoryWarning:));
}
#pragma mark - Method Swizzling
- (BOOL)adean_application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时的操作
// 注册Crash统计 -- Crashlytics
[Fabric
with
:@[[Crashlytics class]]];
// 友盟统计
[MobClick startWithAppkey:UMENG_APPKEY];
[MobClick setCrashReportEnabled:NO];
// 关掉MobClick Crash Report收集开关
#ifdef ADeanForTest
[MobClick setCrashReportEnabled:YES];
// 打开MobClick Crash Report收集开关
[MobClick setLogEnabled:YES];
#endif
[ShareSDK registerApp:ShareSDKAppKey];
//新浪
[ShareSDK connectSinaWeiboWithAppKey:SinaAppKey
appSecret:SinaAppSecret
redirectUri:SinaRedirectUri];
//新浪微博客户端应用
[ShareSDK connectSinaWeiboWithAppKey:SinaAppKey
appSecret:SinaAppSecret
redirectUri:SinaRedirectUri
weiboSDKCls:[WeiboSDK class]];
#if TARGET_IPHONE_SIMULATOR
#else
//QQ好友
[ShareSDK connectQQWithQZoneAppKey:QZoneAppKey
qqApiInterfaceCls:[QQApiInterface class]
tencentOAuthCls:[TencentOAuth class]];
#endif
//微信朋友圈
[ShareSDK connectWeChatSessionWithAppId:WeiXinAppID wechatCls:[WXApi class]];
//微信好友
[ShareSDK connectWeChatTimelineWithAppId:WeiXinAppID wechatCls:[WXApi class]];
});
return
[self adean_application:application didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)adean_application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
[ShareSDK handleOpenURL:url wxDelegate:self];
return
[self adean_application:application handleOpenURL:url];
}
- (BOOL)adean_application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
[ShareSDK handleOpenURL:url sourceApplication:sourceApplication annotation:annotation wxDelegate:self];
return
[self adean_application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
- (void)adean_applicationDidReceiveMemoryWarning:(UIApplication *)application {
[self adean_applicationDidReceiveMemoryWarning:application];
}
@end
|
这下再去看下AppDelegate文件,代码不超过200行了。
小结
Method Swizzling常见的应用场景:
1.用于记录或者存储,比方说记录ViewController进入次数、Btn的点击事件、ViewController的停留时间等等。 可以通过Runtime获取到具体ViewController、Btn信息,然后传给服务器。
2.添加需要而系统没提供的方法,比方说修改Statusbar颜色。
3.用于轻量化、模块化处理,如上面介绍的,代码轻量化处理。
Method Swizzling是把双刃剑,需要正确理解它的使用。
分类增加变量的使用场景:
1.过多继承时,可以通过分类减少继承层级,清晰流程框架。比方说,ViewController可能需要相互冲突的事件,单一父类会导致逻辑复杂。这时候可以通过分类简化逻辑,不同的ViewController引用不同的分类。
2.扩展类属性。
上面我们学习了一些瘦身的技巧,希望通过这些方法写出更可读性更高,可维护性更高的代码。
提醒:
本文涉及到的Demo已经放到GitHub上了。Demo可能与本文有点出入,部分函数命名跟文章中不一致。