iOS 中不要在分類中擴展類的方法

前言

說的比較直白,iOS 中有個好東西,都知道那就是分類Category,有了這個分類咱們可以輕鬆的給基類添加一些功能,更加靈活的添加咱們想要的功能。可是在使用的時候,咱們要注意一點,就是避免去複寫父類的方法,若是不當心複寫了父類方法,可能由此變得亂套了。bash

問題

其實說白了,若是複寫父類的方法,可能會引起父類方法的內容的變動,這樣是極其危險的。爲了驗證這樣的問題,特地寫了一個分類:ui

#import "UIViewController+Add.h"
#import <objc/runtime.h>

@implementation UIViewController (Add)

- (void)viewDidLoad{

    NSLog(@"分類 viewDidLoad");
    
}

複製代碼

而主類中,咱們不導入這個分類,看一下運行結果:spa

#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSLog(@"基類的 ViewDidLoad 方法");
    
    id viewController = objc_getClass("ViewController");
    
    int outCount,i;
    
    Method *methodList = class_copyMethodList(viewController, &outCount);
    
    for (i = 0; i < outCount; i++) {
        Method method = methodList[i];
        NSLog(@"current method is :%@",NSStringFromSelector(method_getName(method)));
    }

}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

複製代碼

結果以下:3d

運行結果

看一下你會發現,在沒有導入分類頭文件的狀況下,分類中的方法被調用了。爲了解釋這個問題,咱們將 controller中的方法用runtime 打印出來,確實只有兩個方法。根據運行的結果,發現子類的 viewDidLoad先被調用,其次父類的 viewDidLoad被調用,彷彿在父類的viewDidLoad中添加了分類的內容。這裏有兩點疑問:code

  • 沒有導入分類頭文件的狀況下爲何可以自動加載並調起分類中的方法;
  • 在方法列表中確實只有兩個方法,那麼 Category 中複寫的方法跟父類到底是什麼關係,是複寫仍是功能的添加

爲了搞懂這兩點,咱們繼續往下深扒~cdn

分析

從打印出來的方法咱們可以看出來ViewController中確實只有一個 ViewDidLoad 方法,因此在methodLists中只有一個與之對應的 SEL。其次,在沒有引入頭文件的狀況下,可以自動調用分類中的方法,只能說明一點,那就是父類中的 ViewDidLoad已經受到分類方法的影響,已經被分類複寫,這裏說判定是複寫是由於在去除[super viewDidLoad]以後,分類中的分類 viewLoad提示已經不在了。blog

這裏解釋了第二點,分類複寫的方法與父類的方法的關係是覆蓋關係,分類方法覆蓋父類的方法。繼承

那麼第一點在沒有導入頭文件的狀況下,爲何分類的方法會被引用呢?get

這一點其實也很好解釋,由於不論有沒有import category 的頭文件,均可以成功調用category的方法,在runtime加載成功以後,Category 已經將擴展中的複寫的方法對原方法進行了替換,import只是幫助了編譯檢查和連接過程。關於runtime 對於 category 的加載流程咱們能夠參考這篇文章objc category的祕密string

結論

對於以上分析,總結來講就是:

  • 分類複寫擴展類的方法,會形成擴展類原始方法的覆蓋,形成整個擴展類原始方法的變動,對整個擴展類的功能形成巨大影響,是一種很危險的行爲;
  • 分類的本質是擴展功能,而不是複寫原始功能,若是須要對原始方法進行擴展,能夠考慮使用繼承或者 hook 方法進行;
  • 不管有沒有導入分類頭文件,runtime在加載完成以後會將分類的擴展方法加入到 methodList方法列表中,導入頭文件的過程只是保證編譯檢查成功以及連接過程順利完成;

擴展(一個類中添加多個分類相同方法,這些分類的方法調用順序)

在一個類中添加多個不一樣分類的相同方法,以下:

#import "LCView+AddOne.h"

@implementation LCView (AddOne)

- (void)testMethod{
    
    NSLog(@"from category one");
}


@end

複製代碼
#import "LCView+AddTwo.h"

@implementation LCView (AddTwo)

- (void)testMethod{
    
    NSLog(@"from category two");
}


@end

複製代碼
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    id testView = objc_getClass("LCView");
    
    unsigned int outCount,i;
    
    Method *methodList = class_copyMethodList(testView, &outCount);
    
    for (i = 0; i < outCount; i++) {
        Method method = methodList[i];
        NSLog(@"current method is :%@",NSStringFromSelector(method_getName(method)));
    }
    
    LCView *test = [LCView new];
    
    [test testMethod];

}

複製代碼

運行以後的結果以下:

可見,若是多個分類擴展添加同一個方法的話,當前methodList中會同時有多個 SEL,而真正調用的時候調用的 SEL 是其中一個,這個調用順序與源文件的編譯順序有關,他們的關係是根據buildPhases->Compile Sources裏面的順序從上至下編譯的,換句話說就是,越排在後面的分類方法將會被實際調用,如圖:

這裏分類AddTwo裏面的方法被調用了。

相關文章
相關標籤/搜索