Method Swizzling 與 Aspect Oriented Programming

一些概念:
Method Swizzling:是改變一個selector的實際實現(xiàn)的技術。通過這一技術,我們可以在運行時通過修改類的分發(fā)表中selector對應的函數(shù),來修改方法的實現(xiàn)。

Aspect Oriented Programming(面向切面編程):一種編程方式,在指定的地方添加一些自定義代碼。
例如一些事務瑣碎,跟主要業(yè)務邏輯無關,在很多地方都有,又很難抽象出來單獨的模塊。這種程序設計問題,業(yè)界給了一個名字:Cross Cutting Concerns
而用 Method Swizzling 動態(tài)給指定的方法添加代碼,以解決 Cross Cutting Concerns 的編程方式就叫:Aspect Oriented Programming(面向切面編程)

初識三種method-swizzing的使用

1.method_exchangeImplementations 方法交換 交換SEL->IMP

@interface Change : NSObject
+ (void)eat;
+ (void)run;
@end

@implementation Change
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method eat = class_getClassMethod(self, @selector(eat));
        Method run = class_getClassMethod(self, @selector(run));
        method_exchangeImplementations(eat, run);
    });
}
+ (void)eat {
    NSLog(@"吃了");
}
+ (void)run {
    NSLog(@"正準備跑");
}
@end

2.class_replaceMethod 取代方法實現(xiàn) IMP

@interface Replace : NSObject
+ (void)sleep;
@end
@implementation Replace
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        class_replaceMethod(self, @selector(sleep), sleepFunction, "");
    });
}
+ (void)sleep {
    NSLog(@"馬上睡");
}
void sleepFunction() {
    NSLog(@"還不想睡");
}
@end

3.class_addMethod 添加方法實現(xiàn) IMP

@interface AddMethod : NSObject
+ (void)work;
@end
@implementation AddMethod
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(work), workFunction, "");
    });
}
void workFunction() {
    NSLog(@"上班");
}
@end

可能出現(xiàn)的錯誤

1.繼承 父類實現(xiàn)了sleep方法 子類沒有實現(xiàn)。當進行方法交換的時候 發(fā)現(xiàn)將父類的方法實現(xiàn)也交換了

@interface Super : NSObject
+ (void)run;
+ (void)sleep;
+ (void)eat;
@end
@implementation Super
+ (void)run {
    NSLog(@"ErrorSuper_跑了");
}
+ (void)sleep {
    NSLog(@"ErrorSuper_睡了");
}
+ (void)eat {
    NSLog(@"ErrorSuper_吃了");
}
@end
@interface Sub : Super
+ (void)run;
+ (void)sleep;
+ (void)eat;
@end
@implementation Sub
+ (void)load {
    //取代實現(xiàn)
    class_replaceMethod(objc_getMetaClass(object_getClassName(self)), @selector(run), replaceRun, "");
    
    //添加實現(xiàn)
    class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), addEat, "");
    
    //交換實現(xiàn)
    method_exchangeImplementations(class_getClassMethod(self, @selector(sleep)), class_getClassMethod(self, @selector(changeMethod)));
}


//取代的實現(xiàn)
void replaceRun() {
    NSLog(@"ErrorSub_取代了跑的實現(xiàn),不想跑");
}

//添加實現(xiàn)
void addEat() {
    NSLog(@"ErrorSub_添加了吃的實現(xiàn),不想吃");
}


//交換的方法
+ (void)changeMethod {
    NSLog(@"ErrorSub_交換的方法");
}
@end

原因:當進行方法交換的時候,Sub并沒有實現(xiàn)sleep方法 那么便會到父類那邊尋找sleep的實現(xiàn)。再進行交換的時候,自然交換的便是父類的sleep方法。
解決:給Sub添加上sleep的實現(xiàn),可以使用class_addMethod動態(tài)添加 在進行交換之前添加

2.多次執(zhí)行方法交換 并沒有出現(xiàn)預期效果
關于這一點,只能說是粗心的原因。
假設:方法1與2 進行交換,預期是調(diào)用1實現(xiàn)2 調(diào)用2實現(xiàn)1
實現(xiàn):交換的函數(shù)執(zhí)行了兩次
分析:
第一次交換,1->2 2->1
第二次交換,1->1 2->2 (在上次的交換中,1的實現(xiàn)是2的實現(xiàn),2 的實現(xiàn)是1的實現(xiàn))。
就是換來換去,結(jié)果就暈了。
所以,很多資料上面都建議。將方法的交換寫在load里面。但是也不能確保不會有人在子類中寫上一個[super load] (手賤)。那么在load中再加上一個dispatch_once

比較完整的方法交換形態(tài)

@interface Complete : NSObject
+ (void)run;
+ (void)eat;
@end
@implementation Complete
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //獲取需要交換的方法
        Method run = class_getClassMethod(self, @selector(run));
        Method eat = class_getClassMethod(self, @selector(eat));
        
        //添加eat方法 為什么要添加 是為了防止自身沒有 而父類有 進行方法交換的時候 交換了父類的方法實現(xiàn) 顯然這不是我們想要的
        //將run的實現(xiàn)添加到 新添加的eat方法上
        BOOL isAdd = class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), method_getImplementation(run), method_getTypeEncoding(run));
        
        //判斷
        if (isAdd) {
            //添加成功 表示自身確實沒有eat方法
            //那么這個時候只需要將eat的方法實現(xiàn) 替換到 run方法上即可
            //是否還記得 判斷之前我們添加過SEL為eat  IMP為run的實現(xiàn) 的eat方法
            class_replaceMethod(self, @selector(run), method_getImplementation(eat), method_getTypeEncoding(eat));
        }else {
            //添加失敗  表示自身存在eat方法
            //那么這個時候只需要將 run 與 eat 的IMP(方法實現(xiàn)) 進行交換就好了
            method_exchangeImplementations(run, eat);
        }
    });
}

+ (void)run {
    NSLog(@"跑了");
}
+ (void)eat {
    NSLog(@"吃了");
}
@end

小例子 利用類別判斷當前是那個控制器 同時也可以做很多事 例如統(tǒng)計

@implementation UIViewController (swizzing)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
        Method swizzMethod = class_getInstanceMethod(self, @selector(swizz_viewWillAppear:));
        
        BOOL isAdd = class_addMethod(self, @selector(viewWillAppear:), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        
        if (isAdd) {
            class_replaceMethod(self, @selector(swizz_viewWillAppear:), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else {
            method_exchangeImplementations(originalMethod, swizzMethod);
        }
    });
}

- (void)swizz_viewWillAppear:(BOOL)animated {
    //這里調(diào)用自身并不會引起遞歸
    //記得兩個方法的實現(xiàn)已經(jīng)進行交換了么
    //這里的調(diào)用 是調(diào)用了系統(tǒng)的方法
    [self swizz_viewWillAppear:animated];
    if ([self isKindOfClass:[ViewController class]]) {
        NSLog(@"當前控制器是ViewController");
    }else if ([self isKindOfClass:[PVC1 class]]) {
        NSLog(@"當前控制器是PVC1");
    }else if ([self isKindOfClass:[PVC2 class]]) {
        NSLog(@"當前控制器是PVC2");
    }else if ([self isKindOfClass:[PVC3 class]]) {
        NSLog(@"當前控制器是PVC3");
    }
}
@end

當然了,實現(xiàn)這種事情,有很多種方法。
但是同時也有很多局限性,可以參考知識鏈接里面的解釋;
在類別中 你沒法 super .... 例如[super viewWillAppear]

Logging 的代碼都很相似,通過繼承或類別重寫相關方法是可以把它從主要邏輯中剝離出來。但同時也帶來新的問題:

1.你需要繼承 UIViewController, UITableViewController, UICollectionViewController 所有這些 ViewController ,或者給他們添加類別;

2.每個 ViewController 里的 ButtonClick 方法命名不可能都一樣;

3.你不能控制別人如何去實例化你的子類;

4.對于類別,你沒辦法調(diào)用到原來的方法實現(xiàn)。大多時候,我們重寫一個方法只是為了添加一些代碼,而不是完全取代它。

5.如果有兩個類別都實現(xiàn)了相同的方法,運行時沒法保證哪一個類別的方法會給調(diào)用。

關于AOP,參考知識鏈接里面的東西。能夠了解的更加透徹
知識鏈接:
AOP
SEL-IMP
Aspects

體驗小Demo:demo

分享一個關于main函數(shù)調(diào)用之前系統(tǒng)所做的一些事情:
http://www.itdecent.cn/p/43db6b0aab8e
https://blog.csdn.net/yu_4074/article/details/54966782

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

相關閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,070評論 0 9
  • 繼上Runtime梳理(四) 通過前面的學習,我們了解到Objective-C的動態(tài)特性:Objective-C不...
    小名一峰閱讀 848評論 0 3
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20161102/17920.html 因為Ob...
    F麥子閱讀 703評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,614評論 19 139
  • 我們有時會很困惑,到底是什么原因促使我們跟眼前這個人走入婚姻的? 很多人會問對方愛不愛自己,認為這個問題是結(jié)婚前最...
    羅掌柜real閱讀 370評論 0 0

友情鏈接更多精彩內(nèi)容