Objective-C Runtime之Method Swizzling

今天準備學習Objective-C Runtime相關知識,看到了Method Swizzling技術,并找到了介紹該技術的文章:雷純鋒的技術博客 。這篇文章貼了一段示例代碼并解釋了三個問題,但是我的問題不止這三個,所以又四處找了相關資料,并將學到的記錄下來。

名詞解釋

Class

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

NSObject的對象都有一個指向Classisa指針。這個 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可以用ClassSEL得到,具體方法是: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 、UITableViewControllerUITabBarController 等不同類型的ViewController 。
也許你跟我一樣陷入了思考,難道就沒有一種簡單優(yōu)雅的解決方案嗎?答案是肯定的,Method Swizzling 就是解決此類問題的最佳方式。
</br>
具體實現(xiàn)請看雷純鋒的技術博客 。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容