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可能与本文有点出入,部分函数命名跟文章中不一致。

时间: 11-15

iOS Method Swizzling和分类的妙用AppDelegate轻量化处理的相关文章

ios method swizzling

阅读器 iOS开发iOS 本文由TracyYih[博客]翻译自NSHipster的文章Method Swizzling. 在上周associated objects一文中,我们开始探索Objective-C运行时的一些黑魔法.本周我们继续前行,来讨论可能是最受争议的运行时技术:method swizzling. Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch tab

iOS 使用Method Swizzling隐藏Status Bar

在iOS 6中,隐藏Status Bar非常的简单. // iOS 6及以前,隐藏状态栏 [[UIApplication sharedApplication] setStatusBarHidden:YES]; 来到了iOS 7的年代以后,需要在UIViewController中指定: #ifdef __IPHONE_7_0 - (BOOL)prefersStatusBarHidden { return YES; } #endif 并通过下列代码刷新状态栏: if ([viewController

iOS执行时与method swizzling

C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序怎样执行的.而Objective-C是动态语言,它并不是通过调用类的方法来执行功能,而是给对象发送消息,对象在接收到消息之后会去找匹配的方法来执行.这样的做法就把C语言在编译时的工作挪到了执行时来做,能够获得额外的灵活性. 在Objective-C中有个@selector,在非常多地方被翻译成"选择子".实际上,对于类的实例对象来说,类的方法是用一个数字来代表的,并不是是我们看到的一个长长的带着:这个字符的一串

iOS黑魔法-Method Swizzling

公司年底要在新年前发一个版本,最近一直很忙,好久没有更新博客了.正好现在新版本开发的差不多了,抽空总结一下.由于最近开发新版本,就避免不了在开发和调试过程中引起崩溃,以及诱发一些之前的bug导致的崩溃.而且项目比较大也很不好排查,正好想起之前研究过的Method Swizzling,考虑是否能用这个苹果的“黑魔法”解决问题,当然用好这个黑魔法并不局限于解决这些问题...... 需求 就拿我们公司项目来说吧,我们公司是做导航的,而且项目规模比较大,各个控制器功能都已经实现.突然有一天老大过来,说我

Objective-C Runtime 运行时之四:Method Swizzling

理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Method Swizzling是改变一个selector的实际实现的技术.通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现. 例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的

Method Swizzling

[Cocoa]深入浅出Cocoa之 Method Swizzling cocoaclassinterfacestructmethodsapi [Cocoa]深入浅出Cocoa之 Method Swizzling 罗朝辉(http://blog.csdn.net/kesalin) CC许可,转载请注明出处 在前文深入浅出Cocoa之消息中,我简要介绍了ObjC 中消息的基本情况,包括SEL查找,缓存以及消息转发等.在本文中,我要介绍一个很有趣的技术,Method swizzling,通过这个手法,

runtime 第四部分method swizzling

接上一篇 http://www.cnblogs.com/ddavidXu/p/5924597.html 转载来源http://www.jianshu.com/p/6b905584f536 http://southpeak.github.io/2014/10/30/objective-c-runtime-2/ runtime的黑魔法,就是可以实现交换两个方法的实现,这就意味着我们可以修改系统的方法实现. 栗子:当UIViewController及其子类的对象调用viewWillAppear时,都会

iOS不得姐项目--appearance的妙用,再一次设置导航栏返回按钮,导航栏左右按钮的封装(巧用分类)

一.UI_APPEARANCE_SELECTOR 彩票项目中appearance的用法一直没有搞明白,这次通过第二个项目中老师的讲解,更深一层次的了解到了很多关于appearance的作用以及使用方法. 在iOS属性后有UI_APPEARANCE_SELECTOR标志都可以一次性统一设置.这种情况还有很多.比如说统一设置UITabbarItem的文字颜色 通过appearance来同意设置属性最好是在+ (void)initialize;方法里面. 项目中设置导航栏背景图片的代码: 项目中设置T

方法调配(method swizzling)

http://www.cocoachina.com/ios/20141002/9819.html http://blog.sina.com.cn/s/blog_a343f32b0101en4o.html OC 是一门及其动态的语言,在运行期间,可以动态给某个对象添加方法,并且还可以改变某个方法实现 我们可以用此特性来干点坏事 通过这个特性,我们可以不需要继承子类,就可以改变这个类本身的一些功能,经常需要统计每个页面浏览的次数 这时我们一般是基于基类baseViewController 在view