iOS Method Swizzling和分类的妙用AppDelegate轻量化处理
http://www.cocoachina.com/ios/20151117/14167.html
简介
在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可能与本文有点出入,部分函数命名跟文章中不一致。
iOS Method Swizzling和分类的妙用AppDelegate轻量化处理的更多相关文章
- ios method swizzling
阅读器 iOS开发iOS 本文由TracyYih[博客]翻译自NSHipster的文章Method Swizzling. 在上周associated objects一文中,我们开始探索Ob ...
- IOS 开发之 Method Swizzling + Category
ios 分类中如果增加的方法与被扩展的类方法名重复,则原方法就没法被调用….看以下例子 例如: @interface ClassA : NSObject - (NSString *) myMethod ...
- ios逆向工程-内部钩子(Method Swizzling)
Method Swizzling(方法调配) 怎么说呢,先了解什么是钩子为什么用钩子,学过C++的朋友应该清楚,hook就是用来获得(截断/改变)底层调用的方法.这样我们可以自由的修改或者读取一些想要 ...
- IOS 开发之 Method Swizzling
ios 分类中如果增加的方法与被扩展的类方法名重复,则原方法就没法被调用….看以下例子 例如: @interface ClassA : NSObject - (NSString *) myMethod ...
- 【原】iOS动态性(三) Method Swizzling以及AOP编程:在运行时进行代码注入
概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...
- iOS中AOP与Method Swizzling 项目中的应用
引子:项目中需要对按钮点击事件进行统计分析,现在项目中就是在按钮的响应代码中添加点击事件,非常繁琐.所以使用了AOP(面向切面编程),将统计的业务逻辑统一抽离出来. 项目中添加的开源库:https:/ ...
- iOS运行时与method swizzling
C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序如何运行的.而Objective-C是动态语言,它并非通过调用类的方 法来执行功能,而是给对象发送消息,对象在接收到消息之后 ...
- iOS 使用Method Swizzling隐藏Status Bar
在iOS 6中,隐藏Status Bar很的简单. // iOS 6及曾经,隐藏状态栏 [[UIApplication sharedApplication] setStatusBarHidden:YE ...
- iOS执行时与method swizzling
C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序怎样执行的.而Objective-C是动态语言,它并不是通过调用类的方法来执行功能,而是给对象发送消息,对象在接收到消息之后 ...
随机推荐
- myeclipse 配置resin
一.新建web project 二.配置本地resin 创建resin/conf/test.conf文件(可从resin.conf copy)中修改 <web-app id="/&qu ...
- 软件-SecureCRT:SecureCRT
ylbtech-软件-SecureCRT:SecureCRT SecureCRT是一款支持SSH(SSH1和SSH2)的终端仿真程序,简单地说是Windows下登录UNIX或Linux服务器主机的软件 ...
- 装配SpringBean(五)--注解装配之自动装配
在spring ioc容器中,spring要先完成bean的定义和生成,然后需要需要注入的资源,在上一篇的扫描组件中就是这样实现的.而自动装配中我们只需要定义和生成一个bean,发现bean的过程将由 ...
- [code]图像亮度调整enhancement
//draft 2013.9 //F=X2/u; ////远处细节被淹没. 亮的地方增亮明显,暗的地方更暗. 不可取. // CvScalar rgb; // rgb=cvAvg(src); //fo ...
- 011-python列表,元组,字典的用法
1.列表 1.1 列表截取 list[头下标:尾下标:步长],例如:L[1:10:2] >>> L [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>& ...
- Spring Cloud Alibaba迁移指南(二):零代码替换 Eureka
自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作<Spring Cloud Alibaba迁移指南>系列文章,向开发者提供更多 ...
- Frank Dellaert Slam Speech 20190708
Georgia Institue of Tecknology 3D Models from Community Databases Spatiotemporal Reconstruction 4D C ...
- 通过游戏学python 3.6 第一季 第六章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改 可复制直接使用 娱乐 可封装 函数
#猜数字--核心代码--猜测次数--随机函数和屏蔽错误代码---优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改 #猜数字--核心代码--猜测次数--随机函数和屏蔽错误代码---优 ...
- TZ_12_Spring的RestTemplate
1.Http客户端工具 HttpClient:HttpClient是Apache公司的产品,是Http Components下的一个组件. 特点: 基于标准.纯净的Java语言.实现了Http1.0和 ...
- 学习线程池源码--ThreadPoolExecutor
1 创建ThreadPoolExecutor ThreadPollExecutor有四个构造函数,但本质上都是调用这一个构造函数. public ThreadPoolExecutor(int core ...