今天準備學習Objective-C Runtime相關知識,看到了Method Swizzling技術,并找到了介紹該技術的文章:雷純鋒的技術博客 。這篇文章貼了一段示例代碼并解釋了三個問題,但是我的問題不止這三個,所以又四處找了相關資料,并將學到的記錄下來。
名詞解釋
Class
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
NSObject的對象都有一個指向Class的isa指針。這個 isa 指針是當你向對象發(fā)送消息時,Objective-C Runtime 檢查一個對象并且查看它的類是什么然后開始查看它是否響應這些 selectors 所需要的一切。Class可以由NSObject對象使用[self class]得到。
</br>
SEL
SEL就是對方法的一種包裝。包裝的SEL類型數(shù)據(jù)它對應相應的方法地址,找到方法地址就可以調用方法。而@selector()得到的就是一個SEL類型對象。
</br>
Method
一個類中的方法。它有兩種類型,一種是實例方法InstanceMethod,如- (void)method;第二種是類方法ClassMethod,如+ (id)alloc。
Method可以用Class和SEL得到,具體方法是:Method class_getInstanceMethod(Class cls, SEL name) 和Method class_getClassMethod(Class cls, SEL name)。
</br>
IMP
IMP是指向方法實現(xiàn)的函數(shù)指針,由編譯器為你生成。
可以用IMP method_getImplementation(Method m)得到Method對象的IMP。
</br>
const char *types
它是一個函數(shù)的返回類型和參數(shù)類型。如函數(shù):void method(id obj, SEL _sel, NSString *str){},它的types為"v@:@",v代表void,@代表NSObject對象,:代表SEL。
可以使用const char *method_getTypeEncoding(Method m)來得到Method對象的types。
</br>
Method Swizzling
方法函數(shù)
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
作用:為cls類增加名為name的方法,具體實現(xiàn)為imp,返回類型與參數(shù)類型為types。
</br>
Method class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
作用:將cls類中名為name的方法實現(xiàn)改為imp,返回類型與參數(shù)類型為types。
</br>
void method_exchangeImplementations(Method m1, Method m2)
作用:交換m1與m2的實現(xiàn)方法。
</br>
示例
1.創(chuàng)建一個類Class1,再一個創(chuàng)建Class1的子類Class2。
2.在Class1中添加代碼:
//Class1.m
- (void)doReplace
{
SEL oldSelctor = @selector(oldFunction:);
SEL newSelctor = @selector(newFunction:);
Method oldMethod = class_getInstanceMethod([self class], oldSelctor);
Method newMethod = class_getInstanceMethod([self class], newSelctor);
BOOL success = class_addMethod([self class], oldSelctor, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
NSLog(@"add method %@", success ? @"success" : @"failure");
if (success) {
class_replaceMethod([self class], newSelctor, method_getImplementation(oldMethod), method_getTypeEncoding(oldMethod));
}else{
method_exchangeImplementations(newMethod, oldMethod);
}
}
- (void)oldFunction:(NSString *)aString
{
NSLog(@"super class old method");
}
- (void)newFunction:(NSString *)aString
{
[self newMethod:aString];
NSLog(@"new method");
}
//main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Class2 *cl2 = [Class2 new];
[cl2 doReplace];
[cl2 oldMethod:@"old"];
}
return 0;
}
原理:首先使用class_addMethod添加名為oldFunction:,實現(xiàn)為newFunction:實現(xiàn)的方法,目的是判斷子類是否實現(xiàn)了oldFunction:方法。
1.子類實現(xiàn)了oldFunction:方法,所以添加方法失敗,直接交換oldFunction:和newFunction:的實現(xiàn)。所以最后在子類中,兩個方法實際上是這樣的:
- (void)oldFunction:(NSString *)aString
{
[self newMethod:aString];
NSLog(@"new method");
}
- (void)newFunction:(NSString *)aString
{
NSLog(@"child class old method");
}
而打印結果則是:
MethodSwizzling[14750:1729063] add method failure
MethodSwizzling[14750:1729063] child class old method
MethodSwizzling[14750:1729063] new method
2.子類沒有實現(xiàn)oldFunction:方法,所以添加方法成功,而oldMethod則得到的是父類的oldFunction:方法,所以之后將newFunction:的實現(xiàn)改為父類oldFunction:方法的實現(xiàn)。所以最后在子類中,兩個方法實際上是這樣的:
- (void)oldFunction:(NSString *)aString
{
[self newMethod:aString];
NSLog(@"new method");
}
- (void)newFunction:(NSString *)aString
{
NSLog(@"super class old method");
}
而打印結果則是:
MethodSwizzling[14770:1730001] add method success
MethodSwizzling[14770:1730001] super class old method
MethodSwizzling[14770:1730001] new method
</br>
應用
以下摘抄自雷純鋒的技術博客 。
</br>
例如使用友盟統(tǒng)計,要實現(xiàn)頁面的統(tǒng)計功能,我們需要在每個頁面的ViewController中添加如下代碼:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[MobClick beginLogPageView:@"PageOne"];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[MobClick endLogPageView:@"PageOne"];
}
要達到這個目的,我們有兩種比較常規(guī)的實現(xiàn)方式:
1. 直接修改每個頁面的ViewController代碼,簡單粗暴;
2. 子類化ViewController,并讓我們的ViewController都繼承這些子類。
第 1 種方式的缺點是不言而喻的,這樣做不僅會產生大量重復的代碼,而且還很容易遺漏某些頁面,非常難維護;第 2 種方式稍微好一點,但是也同樣需要我們子類化 UIViewController 、UITableViewController 和 UITabBarController 等不同類型的ViewController 。
也許你跟我一樣陷入了思考,難道就沒有一種簡單優(yōu)雅的解決方案嗎?答案是肯定的,Method Swizzling 就是解決此類問題的最佳方式。
</br>
具體實現(xiàn)請看雷純鋒的技術博客 。