你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> Method Swizzling和分類的妙用–從AppDelegate輕量化處理說起

Method Swizzling和分類的妙用–從AppDelegate輕量化處理說起

編輯:IOS開發基礎

48651426c0cd13af3f64447c7c24f648.png

簡介

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

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved