转载

Method Swizzling和分类的妙用–从AppDelegate轻量化处理说起

Method Swizzling和分类的妙用–从AppDelegate轻量化处理说起

简介

在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:

// 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中可以按照功能逻辑划分为:处理启动逻辑,注册第三方库,处理其他逻辑三类。这样就可以优化为:

// 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中,通过类模块化便捷使用。这样就可以优化为:

// 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本身进行代码量。

// 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的基本用法。

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

正文到此结束
Loading...