你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS 開發

iOS 開發

編輯:IOS開發基礎

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

簡單的分析下原因

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

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

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

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

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

1834458-fa4863be18be5af0.png.jpeg

.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, 其它的類也是適用的。

這裡介紹了通過攔截器來取代派生基類,但是在需要用繼承的地方法還是需要使用繼承,適當選擇最優的方案才是最明智的, Demo  放在 github ,需要的可以自行下載。


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