說的比較直白,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
爲了搞懂這兩點,咱們繼續往下深扒~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
裏面的方法被調用了。