iOS 開發 -- 使用攔截器來取代基類

gaoxw126 8年前發布 | 5K 次閱讀 iOS開發 移動開發

自從在百度實習開始后,習慣了把 ViewController 里面的一些通用邏輯寫在一個基類,然后其它 ViewController 再繼承這個基類,以前一直都認為這是一個不錯的做法,但今天看了篇關于 View 層的架構文章,完全顛覆了我以前的想法,派生基類并不是最好的選擇。

簡單的分析下原因

  • 派生的基類會增加業務使用的成本

    1. 增加集成成本,在百度實習的時候,開發的 App 依賴于百度地圖和百度導航,而且都是直接源碼依賴進來的,每次編譯一次都好幾分鐘,在添加新的頁面和調試頁面時,需要經常運行查看,單是編譯的時間都讓人無法接受了。想新建一個基于我們開發的 App 環境的 Demo,但我們所有的 ViewController 都繼承于一個基類,而基類又依賴于各種樣的基礎庫,折騰半天也搞不出這么一個 Demo.

    2. 增加學習成本,使用派生的基類時還需要我們去學習派生基類的使用

既然這種方式不是最好的選擇,那當然有更好的方式去取代這種方式來實現相同的效果,下面說下通過攔截器來實現和派生基類一樣的功能。

這里我使用已經造好的輪子 Aspects 來進行方法的攔截,我們來創建一個繼承 NSObject 的 ViewController 的攔截器:

.m 文件:

@implementation ViewControllerInterceptor

// 會在應用啟動的時候自動被runtime調用,通過這個方法可以實現代碼的注入
+ (void)load {
    [super load];
    [ViewControllerInterceptor sharedInstance];
}

// 單例
+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static ViewControllerInterceptor *sharedInstance;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ViewControllerInterceptor alloc] init];
    });
    return sharedInstance;
}

- (instancetype)init {
    if ([super init]) {

    }
    return self;
}

@end

實現一個單例來確保只初始化一次。因為繼承 NSObject,load() 方法就會在啟動時被runtime調用,通過這個方法可以實現代碼的注入。所以我們把 Aspects 的攔截方法實現在 init() 方法里面:

- (instancetype)init {
    if ([super init]) {
         // 使用 Aspects 進行方法的攔截
         // AspectOptions 三種方式選擇:在原本方法前執行、在原本方法后執行、替換原本方法
        [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id





  aspectInfo, BOOL animated){
            UIViewController * vc = [aspectInfo instance];
            [self viewWillAppear:animated viewController:vc];
        } error:NULL];
    }
    return self;
}


  

這里會監聽 UIViewController 的 viewWillAppear: 方法,當 UIViewController 執行 viewWillAppear: 方法后,就會攔截到,然后執行攔截器的模擬 viewWillAppear: 方法:

// 通過這種方式可以代替原來框架中的基類,不必每個 ViewController 再去繼續原框架的基類
#pragma mark - fake methods
- (void)viewWillAppear:(BOOL)animated viewController:(UIViewController *)viewController
{
    // 去做基礎業務相關的內容
    if (!viewController.isInitTheme) {
        [self ThemeDidNeedUpdateStyle];
        viewController.isInitTheme = YES;
    }
    // 其他操作......
}

- (void)ThemeDidNeedUpdateStyle {
    NSLog(@"Theme did need update style");
}

在這里,我想當的 ViewController 執行 viewWillAppear: 方法后判斷是否需要初始化主題,如果已經初始化成功后就會再次執行,所有我們需要在 ViewController 添加一個標志屬性,但 ViewController 是不確定的,我們并不知道當前 ViewController 是哪一個類,如果我每個 ViewController 都添加一個 isInitTheme 的標志,那就又回到派生基類上去了,這時候,就由神奇的 Category 來處理了。

我們對 UIViewControler Category 添加一個 isInitTheme 的屬性:

@interface UIViewController (Addition)

@property(nonatomic, assign) BOOL isInitTheme;

@end

然后再通過 runtime 來動態添加一個 isInitTheme 的實例變量:

#define KeyIsInitTheme @"KeyIsInitTheme"

@implementation UIViewController (Addition)

#pragma mark - inline property
- (BOOL)isInitTheme {
    return objc_getAssociatedObject(self, KeyIsInitTheme);
}

- (void)setIsInitTheme:(BOOL)isInitTheme {
    objc_setAssociatedObject(self, KeyIsInitTheme, @(isInitTheme), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}                 

@end

這里我們就成功在 UIViewController 的 Category 中添加一個實例變量,然后我們就可以使用這個屬性來進行判斷了。

擴展一個問題,當前的代碼是會攔截所有的 ViewController,如果我們想針對某些 ViewController 不攔截又需要怎么辦呢?

其實很簡單,同上面的 isInitTheme 屬性一樣,再添加一個判斷是否需要進行監聽的屬性:

// 攔截器是否有效
@property(nonatomic, assign) BOOL disabledInterceptor;

然后一樣需要通過 runtime 來實現實例變量。然后在 Aspects 攔截成功后進行判斷是否需要下一步的操作:

- (instancetype)init {
    if ([super init]) {
         // 使用 Aspects 進行方法的攔截
         // AspectOptions 三種方式選擇:在原本方法前執行、在原本方法后執行、替換原本方法
        [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id





  aspectInfo, BOOL animated){
            UIViewController * vc = [aspectInfo instance];
            if (!vc.disabledInterceptor) {
                [self viewWillAppear:animated viewController:vc];
            }
        } error:NULL];
    }
    return self;
}


  

在這里,通過攔截來取代派生的基類,這樣的做法的好處是 業務代碼不需要對框架的主動迎合,使得業務能夠被框架感知 ,這里只拿 UIViewControler 來做例子,但不限 UIViewControler, 其它的類也是適用的。

這里介紹了通過攔截器來取代派生基類,但是在需要用繼承的地方法還是需要使用繼承,適當選擇最優的方案才是最明智的,

來自:http://www.cocoachina.com/ios/20161116/18099.html

 

 本文由用戶 gaoxw126 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!