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

  • 相关阅读:
    设计模式中的多态——策略模式详解
    Spring IOC容器启动流程源码解析(一)——容器概念详解及源码初探
    并发包下常见的同步工具类详解(CountDownLatch,CyclicBarrier,Semaphore)
    HNOI2020游记
    旧年之末,新年伊始
    退役V次后做题记录
    PKUWC2020 游记
    CSP2019退役记
    CTS/APIO2019游记
    HNOI2019游记
  • 原文地址:https://www.cnblogs.com/itlover2013/p/4973390.html
Copyright © 2020-2023  润新知