【iOS】category重寫方法的呼叫
前兩天工程中,出現了一個類的兩個分類(自己實現了一個,第三方SDK裡有一個),同時實現了一個方法名相同的方法,當時就產生了一個疑問,當實際呼叫時調的是哪個分類的方法呢?
一、category重寫主類方法
首先先來看看這一點,相信大部分人已經知道,如果一個類的分類重寫了這個類的方法後,那麼這個類的這個方法將失效,起作用的將會是分類的那個重寫方法,在分類重寫的時候Xcode也會給出相應警告:分類實現將會替代主類實現
那麼原方法失效,分類方法生效的原理是?
想弄清這點先來看一下類的初始化,首先oc是動態語言,建立在runtime 的基礎上,同樣類的初始化也是動態的,根類NSObject 的+load
和+initilize
兩個方法,用於類的初始化,我們這裡要著重看的是+load
方法:
+load
方法是當類或分類被新增到 Objective-C runtime 時被呼叫的,實現這個方法可以讓我們在類載入的時候執行一些類相關的行為。子類的 +load
方法會在它的所有父類的 +load
方法執行之後執行,而分類的 +load
方法會在它的主類的 +load
方法執行之後執行。但是不同的類之間的 +load
方法的呼叫順序是不確定的。
原因就在這裡,因為載入順序是父類先+load,然後子類+load,然後分類+load,那麼如果分類重寫子類方法:首先子類+load,將方法新增到類的方法列表methodLists,然後分類+load,將重寫的方法新增到方法列表中,但是這裡存在幾點疑問:
1. 方法列表methodLists裡是否會有兩個SEL相同的方法?
2. 如果會有,這兩個方法在方法列表中的順序是怎樣的?(順序決定哪個被呼叫)
下面來求證一下:
#import "TestCategory.h"
/*主類實現*/
@implementation TestCategory
- (void)newMethod {
NSLog(@"主類");
}
@end
#import "TestCategory+add.h"
/*分類一實現*/
@implementation TestCategory (add)
- (void)newMethod {
NSLog(@"分類一");
}
@end
#import <objc/runtime.h>
#import "TestCategory.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id LenderClass = objc_getClass("TestCategory");
unsigned int outCount, i;
//獲取例項方法列表
Method *methodList = class_copyMethodList(LenderClass, &outCount);
for (i=0; i<outCount; i++) {
Method method = methodList[i];
NSLog(@"instanceMethod:%@", NSStringFromSelector(method_getName(method)));
}
//呼叫一下
TestCategory *tCategory = [[TestCategory alloc]init];
[tCategory newMethod];
}
看輸出:
2017-07-19 21:38:13.593 TestRuntimeProperty[27827:1306334] instanceMethod:newMethod
2017-07-19 21:38:13.593 TestRuntimeProperty[27827:1306334] instanceMethod:newMethod
2017-07-19 21:38:13.594 TestRuntimeProperty[27827:1306334] 分類一
可見:
1. 方法列表裡會存在兩個SEL相同的方法。
2. 實際呼叫時,呼叫的是後新增的方法,即後新增的方法在方法列表methodLists的這個陣列的頂部
答案已經很明確了:後+load的類的方法,後新增到方法列表,而這時的新增方式又是插入頂部新增,即[methodLists insertObject:category_method atIndex:0];
所以objc_msgSend遍歷方法列表查詢SEL 對應的IMP時,會先找到分類重寫的那個,呼叫執行。然後新增到快取列表中,這樣主類方法實現永遠也不會調到。
(注:methodLists和method的結構定義可以看下我上篇文章-【iOS】Runtime解讀)
二、多個category實現同一個方法
但是如果多個分類同時重寫同一個方法,執行順序又是怎樣的呢?
答案是:對於多個分類同時重寫同一個方法,Xcode在執行時是根據buildPhases->Compile Sources裡面的順序從上至下編譯的,那麼很明顯就像子類和分類一樣,後編譯的後load,即後新增到方法列表,所以後編譯的分類,方法會放到方法列表頂部,呼叫的時候先執行。
新增程式碼驗證一下:
#import "TestCategory+addAgain.h"
/*分類二實現*/
@implementation TestCategory (addAgain)
- (void)newMethod {
NSLog(@"分類二");
}
@end
看輸出:
2017-07-19 22:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod:newMethod
2017-07-19 22:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod:newMethod
2017-07-19 21:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod:newMethod
2017-07-19 22:18:13.594 TestRuntimeProperty[28385:1331972] 分類一
結果輸出仍然是分類一
,那就說明"TestCategory+add.h"
在buildPhases->Compile Sources裡面的順序是靠下的,看下buildPhases的確如此:
總結一句話:類的載入順序,決定方法的新增順序,呼叫的時候,後新增的方法會先被找到,所以呼叫的始終是後載入的類的方法實現。
相關文章
- iOS 重寫cell的FrameiOS
- IOS category 與 extensioniOSGo
- 方法重寫
- 關於iOS Class Category的整理iOSGo
- 陣列與方法的呼叫(重點)陣列
- iOS底層原理-CategoryiOSGo
- iOS設計模式——CategoryiOS設計模式Go
- java之方法的重寫Java
- 重寫equals()方法時,需要同時重寫hashCode()方法
- iOS Extension Category Protrol 例子理解iOSGo
- java方法的神奇修改(重寫)Java
- 方法重寫(Override)IDE
- java 方法重寫概念Java
- 建議重寫equals方法時也一併重寫hashCode方法
- Java重寫equals方法時為什麼要重寫hashcode方法Java
- Python呼叫C++編寫的方法PythonC++
- JavaBean重寫Object類中的方法JavaBeanObject
- 方法重置和重寫的區別
- iOS問題整理03----CategoryiOSGo
- iOS套路面試題之CategoryiOS面試題Go
- iOS使用Category新增@property變數iOSGo變數
- iOS底層原理總結 - Category的本質iOSGo
- 在 iOS 中實現方法鏈呼叫iOS
- Java 面試題關於方法的重寫Java面試題
- Spring @Retryable重試方法呼叫詳解Spring
- iOS runtime 給 Category 加屬性iOSGo
- java 新建立的類要重寫的方法Java
- Java中方法重寫與方法過載Java
- iOS runtime實用篇--監聽方法的呼叫iOS
- Category的本質<二>load,initialize方法Go
- Android ListView的getview()中重複呼叫(position重複呼叫)AndroidView
- JavaScript如何呼叫Native iOS/Android 方法JavaScriptiOSAndroid
- H5 呼叫 Android 和 iOS 方法H5AndroidiOS
- IOS呼叫相簿顯示英文解決方法iOS
- 關於ListView的getView方法被多次重複呼叫的問題View
- 為什麼重寫 equals() 方法,一定要重寫 hashCode() 呢?| HashMapHashMap
- 重寫JS中的apply,call,bind,new方法JSAPP
- 重寫Java的String及其大部分方法Java