前言
Runtime是iOS開發者進階必須學習的一個知識點。網上關于Runtime 有許多介紹,有深入有簡單介紹,也有實際應用舉例,但是都不夠系統,相關的知識點得不到關聯,對runtime 的認知不能形成一個體系。這里參照蘋果官方文檔,加上自己的一些理解,進行了系統的介紹總結。 文章篇幅很長,前面很大一部分是概念介紹有點枯燥,如果想直接學習runtime的使用可以直接查看后面的常見使用介紹,或者下載我寫的Demo:WXSRuntime 歡迎star 下載。 (歡迎轉載,轉載注明出處)
目錄
一、Runtime版本與平臺介紹
二、使用Runtime的場景
1、Objective-C Source Code 2、NSObject Methods 3、Runtime Functions
三、消息機制(Messaging)
1、objc_msgSend 函數 2、使用隱藏的參數 3、獲得方法地址
四、動態方法決議
1、動態方法決議(Dynamic Method Resolution) 2、動態加載(Dynamic Loading)
五、消息轉發
1、消息轉發(Forwarding) 2、消息轉發與多繼承(Forwarding and Multiple Inheritance) 3、代理對象(Surrogate Objects) 4、消息轉發與繼承(Forwarding and Inheritance)
六、Type Encodings
七、聲明屬性(Declared Properties)
1、屬性類型和函數(Property Type and Functions) 2、屬性類型字符串(Property Type String) 3、屬性特性(Property Attribute)例子
正文
一、Runtime版本與平臺介紹
Runtime 有兩個版本 一個Legacy版本 ,一個Modern版本。Legacy版本用于Objective-C 1,32位 的OS X的平臺上,Modern版本適用于Objective-C 2,iOS 和 OS X v10.5 及之后版本。很明顯,我們現在采用的Runtime 為Modern版本。
二、使用Runtime的場景
OC程序使用Runtime 系統有三種情景:Objective-C Source Code、NSObject Methods、Runtime Functions;
1、Objective-C Source Code
大部分時候runtime是在幕后運行工作著的。在編譯含有OC類、方法的代碼時,編譯器通過Runtime的消息機制在幕后完成創建數據、調用函數。Runtime的實質是消息的發送,官方文檔稱之為Messaging,翻譯成中文為消息機制。消息機制在OC源碼使用過程中會被調用。
2、NSObject Methods
官方文檔原文的描述:
Some of the NSObject methods simply query the runtime system for information. These methods allow objects to perform introspection. Examples of such methods are the class method, which asks an object to identify its class; isKindOfClass: and isMemberOfClass:, which test an object’s position in the inheritance hierarchy; respondsToSelector:, which indicates whether an object can accept a particular message; conformsToProtocol:, which indicates whether an object claims to implement the methods defined in a specific protocol; and methodForSelector:, which provides the address of a method’s implementation. Methods like these give an object the ability to introspect about itself. Cocoa中絕大多數的類繼承自NSObject,因此繼承了NSObject的方法,其中一些方法可以查詢Runtime系統的相關信息。例如: isKindOfClass: 用來判斷是否是某個類或其子類的實例isMemberOfClass: 用來判斷是否是某個類的實例 respondsToSelector: 用來判斷是否有以某個名字命名的方法(被封裝在一個selector的對象里傳遞)instancesRespondToSelector: 用來判斷實例是否有以某個名字命名的方法conformsToProtocol: 判斷是否遵循相關協議 methodForSelector: 可以獲得一個指向方法實現的指針,并可以使用該指針直接調用方法實現
3、Runtime Functions
Runtime系統是一個C語言動態庫,在Xcode的/usr/include/objc里可以看到由一些列函數和數據結構構成的公共接口,里面的許多函數可以用C語言去調用。這些函數可以在開發環境中用來生成一些工具,在一些功能場景中也可以用到。關于這些函數的詳細介紹可以查看Objective-C Runtime Reference
三、消息機制(Messaging)
1、objc_msgSend 函數
在OC中,調用方法: [receiver message] 在編譯器里會轉換成消息機制里的消息發送形式:objc_msgSend(receiver, selector) 如果帶參數的話: objc_msgSend(receiver, selector, arg1, arg2, ...) 消息功能為動態綁定做了很多有必要的工作: 1、通過selector 在 消息接收者 class 里選擇方法實現(method implementation) 2、調用方法實現,傳遞到接收對象 with 參數 3、傳遞方法實現返回值。 Note:編譯器才能啟用消息函數,我們的代碼不可以。 為了讓編譯器編譯時,消息機制與類的結構關聯上,每個類的結構里添加了兩個基本的元素: 1、指向父類的指針(isa指針); 2、類調度表(A class dispatch table),通過Selector方法名在dispatch table里面匹配對應的方法地址(class-specific address)。 當一個對象被創建、分配內存時,它的實例里的變量會初始化,里面有一個指向它的類的結構體的指針,isa指針。 Paste_Image.png 消息發送到一個對象時,通過class結構體里的isa指針在dispatch table里尋找相應的 selector,如果找不到便進入其父類里找,一直到NSObject. 一旦定位到selector ,便調用該方法,傳遞相關數據。為了提高效率,Runtime 會緩存調用過的selector 和方法地址,在到disaptch table查找之前,先到Catch里查找。
2、使用隱藏的參數
在objc_msgSend運作過程中,在傳送消息中傳遞所有參數,包括兩個隱藏起來的參數: 1、接收對象 2、方法的selector 這兩個參數為每個方法的實現提供調用時的相關信息,之所以說是被隱藏是因為在代碼中不用聲明這兩個參數,沒有體現出這兩個參數,他們被嵌入在方法的實現中。 一個方法中,接收對象為self,它的方法選擇器selector為_cmd,如下例子中,_cmd為strange方法的selector,self為接收strange方法的對象。
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
3、獲得方法地址
繞過動態綁定的唯一方法只有獲取方法地址然后直接調用。在某些場景下,需要連續執行一個方法多次,如果普通的調用會讓每次執行該方法時相應的消息發送內容都重寫一次,這里我們可以使用NSObject里面一個方法methodForSelector:防止消息每次都重寫。 方法methodForSelector:可以讓指針指向方法對應的實現,接著使用指針調用程序,執行方法。該方法的指針必須返回適當的函數類型,為了以防萬一,返回和參數類型都應該包含進去。
void (setter)(id, SEL, BOOL);
int i;
setter = (void ()(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
傳到程序(procedure)的消息中的前兩個參數 是接收對象(self)和 方法選擇器(_cmd)。這兩個參數在方法語句中被隱藏了,但是當方法作為函數被調用時必須明確存在。 使用methodForSelector:方法繞過動態綁定可以節省很大一部分消息發送的時間。但是,只有在一個特定的消息重復許多次時,方法才會被簽名認證,如上面例子中的for循環。平時開發中,我們可以在一些循環中采用methodForSelector:方法提高代碼運行效率。 Using methodForSelector: to circumvent dynamic binding saves most of the time required by messaging. However, the savings will be significant only where a particular message is repeated many times 值得注意的是methodForSelector:方法是runtime系統中提供得方法,不是Objective-C語言自身的特性。
四、動態方法決議
1、動態方法決議(Dynamic Method Resolution)
在一些情景下我們想要動態實現方法,例如用@dynamic聲明屬性特征, @dynamic propertyName; 這行代碼告訴編譯器,與這個屬性相關的方法動態實現。 我們可以通過resolveInstanceMethod: 和 resolveClassMethod: 動態實現一個實例和類的selector。 一個OC方法其實是一個至少帶有self(接受對象)和 _cmd(執行的selector)的 C語言函數.我們可以通過class_addMethod:將一個函數添加成一個類中的方法,而添加的過程可以在resolveInstanceMethod:中添加
@implementation MyClass
- (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
轉發消息和動態決議是緊密相關的。一個類在消息轉發機制(forwarding mechanism)結束之前有一個機會動態處理一個方法,調用respondsToSelector:和instancesRespondToSelector:會先為selector提供IMP,從而可以進行動態方法處理。 如果你實現了resolveInstanceMethod:這個方法,但是想讓某些selector通過消息轉發機制轉發,在實現中判斷如果是這些selector 就return NO;
2、動態加載(Dynamic Loading)
OC程序可以在運行時加載和鏈接新的類,新加入的代碼會合并進程序中,與一開始加載的類或類別同等對待。 動態加載可以用來做很多事情。例如,App的系統設置便是動態加載。 在Cocoa環境中,常用于App功能的自定義定制。你的程序可以在運行時加載其他模塊,例如 Interface Builder加載顏色,和OS X系統設置應用加載偏好設置模塊。動態加載模塊擴展了應用的功能。 You provide the framework, but others provide the code. Runtime 提供了動態加載的方法objc_loadModules,但是Cocoa里的NSBundle提供了更方便的接口,可以在到這里NSBundle 看看其用法。
五、消息轉發
Sending a message to an object that does not handle that message is an error. However, before announcing the error, the runtime system gives the receiving object a second chance to handle the message. 當一個對象不能正常及時處理發送過來的消息時會導致異常,runtime系統在發生異常之前提供了第二次機會處理消息的機會。
1、消息轉發(Forwarding)
如果一個對象沒有正常處理發送過來的消息,在異常之前 Runtime 向對象發送帶有 NSInvocation對象作為基礎參數的forwardInvocation: 消息,NSinvoction對象收納了初始消息和參數。 我們可以實現forwardInvocation:方法做為對消息的默認回應,防止發生異常。forwardInvocation:正如其名字所形容的,就是轉發消息到其他對象。 假如你想設計一個能響應negotiate方法的對象a,而negotiate方法的實現是在其他對象b中。你可以通過發送negotiate消息到實現這個方法的類b中去,簡單地完成這個功能。 更進一步,假設你想要這個對象a能精確地(exactly)執行negotiate方法,一個方法是繼承。但是它不可能在兩個類的對象中傳值。 我們可以通過實現一個方法簡單地傳送消息到類b的實例中去“借”negotiate方法。
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
用這個方法會帶來一些麻煩,對于每一個你想"借"的方法,你需要實現一個方法去獲得,而且對于一些未知的方法,你無法去處理。 通過forwardInvocation:方法可以解決這個問題,這里我們可以采用動態的方式而不是靜態。主要工作過程為:當一個對象因為沒有匹配的selector方法而不能響應一個消息時,runtime系統向這個對象發送forwardInvocation:消息,每一個對象都從NSObject繼承了forwardInvocation:方法,但是在NSObject中,這個方法結束后直接調用了doesNotRecognizeSelector:。我們必須自己重寫forwardInvocation:方法執行之后相關的實現。這樣我們就可以利用forwardInvocation:方法轉發消息到其他類中去。 我的github這里面的Messaging文件是消息轉發相關例子,注釋中有進行了說明。 轉發一個消息時,forwardInvocation:方法中需要做到: 1、確定消息發送到何處。 2、發送時帶上原始參數。 消息可以通過invokeWithTarget:方法發送 - (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
返回值類型可以被傳回到原始的傳送者(sender)中,包括 id類型,結構體,雙精度浮點數。 可以把forwardInvocation:方法當做無法辨認的消息的分發中心,將消息分配給各個接收者,它可以把所有消息發送到同一個目的地,也可以聯合幾個消息做出同一個響應。forwardInvocation:方法主要面向方法實現,但是它提供的通過消息轉發鏈鏈接對象的機會為程序設計帶來更多可能性。 Note: forwardInvocation: 只有在調用了不存在的方法導致消息無法處理時才會被調用。 可以到Foundation框架中查看NSInvocation的相關文檔,獲取更多的詳細內容。
2、消息轉發與多繼承(Forwarding and Multiple Inheritance)
消息轉發參照了繼承,在OC程序中可以借用一些多繼承的功能。 在下圖中,一個對象對一個消息做出回應,類似于借來一個在其他類定義實現的方法。 Paste_Image.png 在這個插圖中,warrior實例轉發了一個negotiate消息到Diplomat實例中,執行Diplomat中的negotiate方法,結果看起來像是warrior實例執行了一個和Diplomat實例一樣的negotiate方法,其實執行者還是Diplomat實例。 在上面的例子中,看起來相當于Warrior類繼承了Diplomat。 The object that forwards a message thus “inherits” methods from two branches of the inheritance hierarchy—its own branch and that of the object that responds to the message 消息轉發提供了許多類似于多繼承的特性,但是他們之間有一個很大的不同: 多繼承:合并了不同的行為特征在一個單獨的對象中,會得到一個重量級多層面的對象。 消息轉發:將各個功能分散到不同的對象中,得到的一些輕量級的對象,這些對象通過消息通過消息轉發聯合起來。
3、代理對象(Surrogate Objects)
消息轉發 不僅參照了多繼承,它還讓用輕量級對象代替重量級對象成為了可能。 通過代理(Surrogate)可以為對象篩選消息。 代理管理發送到接收者的消息,確定參數值被復制,拯救等等。但是它不企圖去做很多其他的,它不重復對象的功能只是簡單地提供對象一個可以接收來自其他應用消息的地址。 舉個例子,有一個重量級對象,里面加入了許多大型數據,如圖片視頻等,每次使用這個對象的時候都需要讀取磁盤上的內容,需要消耗很多時間(time-consuming),所以我們更偏向于采用懶加載模式。 在這樣的情況下,你可以初始化一個簡單的輕量級對象來代理(surrogate)它。利用代理對象可以做到例如查詢數據信息等,而不用加載一整個重量級對象。如果是直接用重量級對象的話,它會一直被持有占用資源。當代理的forwardInvocation:方法第一次接收消息的時候,它會確保對象是否存在,如果不存在邊創建一個。 當這個代理對象發送的消息覆蓋了這個重量級對象的所有功能時,這個代理對象就相當于和重量級對象一樣。 創建一個輕量級的對象來代理一個重量級對象,完成相對應的功能,而不用一直持有著重量級對象,從而可以減少資源占用。
4、消息轉發與繼承(Forwarding and Inheritance)
盡管消息轉發參照了繼承,但是NSObject 不會混亂 像 respondsToSelector: 和 isKindOfClass: 這些方法只有在繼承體系里看到,不會出現在消息轉發鏈里。 例如, Warrio 對象是否響應negotiate
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
大多情況下答案是No,盡管它能無錯誤地收到negotiate消息或者某種意義上通過轉發到Diplomat來響應。 如果我們想通過消息轉發設立一個代理對象或擴展類的功能,消息轉發體質就得像繼承一樣清晰顯然。如果我們想讓對象看起來真正地繼承了父類對象的行為特征,我們需要去重寫respondsToSelector: 和isKindOfClass:方法。 - (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
除了respondsToSelector: 和 isKindOfClass:兩個方法外,instancesRespondToSelector方法也映射著消息轉發規則。當涉及到協議時我們需要還要考慮conformsToProtocol:。同樣,一個對象轉發消息時,將會執行methodSignatureForSelector:,它描述了方法的簽名認證等相關信息,如果一個對象能夠轉發詳細到它的代理,我們需要實現methodSignatureForSelector:方法。 - (NSMethodSignature)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
注意:這是一個高級用法,只適用于沒有其他可能解決方案的情況下使用,不建議用它來代替繼承。當你使用這個方法的時候必須保證你完全熟悉類相關的行為特征以及消息轉發的情況。 里面涉及的一些方法可以到NSObject 和NSInvocation中去查閱更詳細的內容
六、Type Encodings
為了協助runtime 系統,編譯器為每個方法用字符串 編碼 返回和參數 類型,并將字符串與方法選擇器相關聯。所使用的編碼表同樣在其他context里是有用的,所以它是公共可用的with@encode()編譯程序指令。提供一個類型說明時,@encode()返回一個編碼這個類型的字符串。可以是基礎類型,int, 指針,結構體,聯合體。或一個類,事實上,可以用來做C語言sizeof()操作的參數
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char buf3 = @encode(Rectangle);
下面是類型編碼表。里面有許多是與archive 和 distribution 編碼時重復的編碼,但是 可以到NSCoder查閱更詳細的內容 Objective_C type encodings
重要:OC不支持long double 類型,@encode(long double)將會返回d,即double類型;
數組的類型編碼是在方括號里面的包含一個代表元素個數的數字和一個數組元素的類型編碼,例如一個包含12個浮點數的數組:
[12^f]
結構體類型編碼的顯示是一個大括號里面包含名稱和變量的類型編碼,例如:
typedef struct example {
id anObject;
char aString;
int anInt;
} Example;
這個結構體的類型編碼是:
{example=@i}
指向這個結構體的結構體指針類型編碼為:
^{example=@i}
還有另外一種去除了內部的說明:
^^{example}
對象(Object)與結構體類似,例如NSObject的編碼:
{NSObject=#}
NSObject只聲明一個實例變量:isa,它是一個Class。 該注意的是,盡管@encode()指令不返回這些編碼,但是當他們在協議中聲明的方法中被使用到時,runtime系統為類型限定符提供了另外的編碼,如下圖。 Objective-C method encodings
七、聲明屬性(Declared Properties)
編譯器在屬性聲明(Property declarations)的時候,它生成與類、類別、協議相關聯的元數據,我們可以通過這些元數據使用這些函數:通過名字查看屬性、得到屬性的類型(@encode串形式)、復制出屬性的相關參數(C語言字符串形式)列表等。
1、屬性類型和函數(Property Type and Functions)
可以使用class_copyPropertyList和 protocol_copyPropertyList 獲取屬性數組
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
例如:有這么一個類如下:
@interface Lender : NSObject {
float alone;
}
@property float alone;
@end
可以獲得其屬性隊列:
id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
獲取屬性名稱:
const char *property_getName(objc_property_t property)
可以通過一個已知名字的Class或協議獲取屬性
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
可以獲取屬性的相關特性,特性中包括了許多信息。例如類型編碼字符串等,下面的章節中有具體講解。
const char *property_getAttributes(objc_property_t property)
將以上的函數結合在一起,可以打印出類中所有的屬性的信息:
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
2、屬性類型字符串(Property Type String)
可以用property_getAttributes函數得到屬性的名字、類型編碼字符串、以及特性。 字符串以T開頭,后面接著@encode類型編碼,接著是逗號,接著是V,接著是屬性名,在這中間,使用下面這個表中的符號,用逗號隔開。 Declared property type encodings.png 具體例子看下面的屬性特性例子。
3、屬性特性(Property Attribute)例子
先預處理:
enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };
下面是屬性類型編程字符串的例子: Paste_Image.png Paste_Image.png
常見使用:
runtime 常見的使用有:
動態交換兩個方法的實現
實現分類也可以添加屬性 實現NSCoding的自動歸檔和解檔 實現字典轉模型的自動轉換 Hook 這里是代碼完整版WXSRuntime
動態交換兩個方法的實現
//交換實例方法
NSLog(@"------exchange-----\n");
Method m1 = class_getInstanceMethod([ShowExchange class], @selector(firstMethod));
Method m2 = class_getInstanceMethod([ShowExchange class], @selector(secondMethod));
method_exchangeImplementations(m1, m2);
ShowExchange *test = [ShowExchange new];
[test firstMethod];
NSLog(@"------exchange InstanceMethod-----\n");
##實現分類也可以添加屬性
-(void)setWxsTitle:(NSString )wxsTitle { objc_setAssociatedObject(self, WXSAddPropertyKeyTitle, wxsTitle, OBJC_ASSOCIATION_RETAIN); } -(NSString )wxsTitle { return objc_getAssociatedObject(self, WXSAddPropertyKeyTitle); }
##實現NSCoding的自動歸檔和解檔
unsigned int outCount = 0; Ivar ivars = class_copyIvarList(self.class, &outCount); for (int i = 0; i< outCount; i++) { Ivar ivar = ivars[i]; const char ivarName = ivar_getName(ivar); NSString ivarNameStr = [NSString stringWithUTF8String:ivarName]; NSString setterName = [ivarNameStr substringFromIndex:1];
//解碼
id obj = [aDecoder decodeObjectForKey:setterName]; //要注意key與編碼的key是一致的
SEL setterSel = [self creatSetterWithKey:setterName];
if (obj) {
((void (*)(id ,SEL ,id))objc_msgSend)(self,setterSel,obj);
}
}
free(ivars);
##實現字典轉模型的自動轉換
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *key = [NSString stringWithUTF8String:propertyName];
id value = nil;
if (![dict[key] isKindOfClass:[NSNull class]]) {
value = dict[key];
}
unsigned int count = 0;
objc_property_attribute_t *atts = property_copyAttributeList(property, &count);
objc_property_attribute_t att = atts[0];
NSString *type = [NSString stringWithUTF8String:att.value];
type = [type stringByReplacingOccurrencesOfString:@"“" withString:@""];
type = [type stringByReplacingOccurrencesOfString:@"@" withString:@""];
NSLog(@"type%@",type);
//數據為數組時
if ([value isKindOfClass:[NSArray class]]) {
Class class = NSClassFromString(key);
NSMutableArray *temArr = [[NSMutableArray alloc] init];
for (NSDictionary *tempDic in value) {
if (class) {
id model = [[class alloc] initWithDic:tempDic];
[temArr addObject:model];
}
}
value = temArr;
}
//數據為字典時
if ([value isKindOfClass:[NSDictionary class]] && ![type hasPrefix:@"NS"] ) {
Class class = NSClassFromString(key);
if (class) {
value = [[class alloc] initWithDic:value];
}
}
// 賦值 SEL setterSel = [self creatSetterWithKey:key]; if (setterSel != nil) { ((void (*)(id,SEL,id))objc_msgSend)(self,setterSel,value); }
}
## Hook
(void)viewDidLoad { [super viewDidLoad]; Method m1 = class_getInstanceMethod([self class], @selector(viewWillAppear:)); Method m2 = class_getInstanceMethod([self class], @selector(wxs_viewWillAppear:));
BOOL isSuccess = class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(m2), method_getTypeEncoding(m2)); if (isSuccess) {
// 添加成功:說明源方法m1現在的實現為交換方法m2的實現,現在將源方法m1的實現替換到交換方法m2中
class_replaceMethod([self class], @selector(wxs_viewWillAppear:), method_getImplementation(m1), method_getTypeEncoding(m1));
}else {
//添加失敗:說明源方法已經有實現,直接將兩個方法的實現交換即
method_exchangeImplementations(m1, m2);
} } -(void)viewWillAppear:(BOOL)animated { NSLog(@"viewWillAppear"); }
(void)wxs_viewWillAppear:(BOOL)animated { NSLog(@"Hook : 攔截到viewwillApear的實現,在其基礎上添加了這行代碼"); [self wxs_viewWillAppear:YES]; }