iOS 黑魔法之Method-Swizzling

Method-Swizzle

Method Swizziling 是OC運行時給我們的用于交換Method的實現(xiàn)方式(IMP)的能力。
其用到的核心方法就是method_exchangeImplementations,代碼存在于objc-runtime-new.mm

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    mutex_locker_t lock(runtimeLock);

    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil);

    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}

原理實現(xiàn)圖

method-swizzling.png

經(jīng)過上述我們就知道蛤售,當經(jīng)過Method-Swizzling后原本兩個方法的實現(xiàn)IMP得到了互換誓焦。

具體使用

我們準備連個文件.h和兩個.m文件;
LGPerson表示如下

#import <Foundation/Foundation.h>

@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

LGPerson.m表示如下

@implementation LGPerson
- (void)personInstanceMethod{
    NSLog(@"person對象方法:%s",__func__);
}
- (void)personClassMethod{
    NSLog(@"person類方法:%s",__func__);
}
@end

LGPerson+LG.h表示如下

#import "LGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson (LG)


@end

NS_ASSUME_NONNULL_END

LGPerson+LG.m表示如下

#import "LGPerson+LG.h"
#import <objc/runtime.h>

@implementation LGPerson (LG)
+(void)initialize{
    static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
            
            SEL defaultSelector = @selector(personInstanceMethod);
            SEL swizzledSelector = @selector(MonitoringCallPersonInstanceMethod);
            
            Method defaultMethod = class_getInstanceMethod(class, defaultSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            
            method_exchangeImplementations(defaultMethod, swizzledMethod);
        });
}

-(void)MonitoringCallPersonInstanceMethod{
    NSLog(@"現(xiàn)在是MonitoringCallPersonInstanceMethod被調(diào)用了哦");
}
@end

當我們調(diào)用

LGPerson *p = [[LGPerson alloc] init];
[p personInstanceMethod];

輸出了

現(xiàn)在是MonitoringCallPersonInstanceMethod被調(diào)用了哦

是不是現(xiàn)在有了一個大概的認識

method-swizzling 使用時注意點

  • 1诺祸、使用過程中保證一次性,為什么要保持一次呢携悯?如果方法交換兩次的時候不就交換回來了嗎,所以避免重復交換可通過dispatch_once來實現(xiàn),大家可以從我的LGPerson+LG.m例子中看出

我這里沒有將方法添加到+load筷笨,是因為保持OC對象的懶加載特性憔鬼,讓我們的應用啟動變快

  • 2龟劲、子類實現(xiàn)的方法交換的時候,被交換的方法來自父類;如果這樣就會出問題轴或;要保證交換的方法是來自當前本類
    • 2.1當這種情況發(fā)生后昌跌,父類的其他子類也會調(diào)用實現(xiàn)method-swizzling的方法,這種侵入性太大了照雁。
    • 2.2 當子類中維持父類方法的調(diào)用我們會這樣寫(LGStudent是LGPerson的子類)
@implementation LGStudent (LG)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
           Class class = [self class];
            
            SEL defaultSelector = @selector(personInstanceMethod);
            SEL swizzledSelector = @selector(lg_studentInstanceMethod);
            
            Method defaultMethod = class_getInstanceMethod(class, defaultSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            
            method_exchangeImplementations(defaultMethod, swizzledMethod);
    });
}

- (void)lg_studentInstanceMethod{
    [self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
    NSLog(@"LGStudent分類添加的lg對象方法:%s",__func__);
}

@end

那么當父類或者父類的其他子類調(diào)用就會出現(xiàn)

image.png

為了避免這個問題蚕愤,一般方式是給類添加方法class_addMethod

  • 如果添加成功,即當前類是沒有這個方法的,可以通過class_replaceMethod進行替換饺蚊,這樣就不會修改父類和其他子類
    -如果添加失敗,即當前類是存在該方法的萍诱,則可以通過method_exchangeImplementations進行方法交換

一個比較合理的方法交換實現(xiàn)

+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"傳入的交換類不能為空");
    // oriSEL       personInstanceMethod
    // swizzledSEL  lg_studentInstanceMethod
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
   
    // 嘗試添加你要交換的方法 - lg_studentInstanceMethod
    BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));

    /**
     personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
     lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
     */
    
    if (success) {// 自己沒有 - 交換 - 沒有父類進行處理 (重寫一個)
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{ // 自己有
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    
    
}

Method-Swizzling 使用場景(黑魔法確實是黑魔法)

1、在逆向中可以對方法下hook
2污呼、監(jiān)聽方法的調(diào)用
3裕坊、面向切換編程AOP(Aspect Oriented Programming),在某個方法調(diào)用前,檢測參數(shù)是否合理燕酷,不合理做一些防崩潰處理;常見的是[NSArray objectIndex:]籍凝,檢測數(shù)組是否越界,如果越界則直接返回nil悟狱,如果沒有越界静浴,則正常調(diào)用

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挤渐,隨后出現(xiàn)的幾起案子苹享,更是在濱河造成了極大的恐慌,老刑警劉巖浴麻,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件得问,死亡現(xiàn)場離奇詭異,居然都是意外死亡软免,警方通過查閱死者的電腦和手機宫纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膏萧,“玉大人漓骚,你說我怎么就攤上這事¢环海” “怎么了蝌蹂?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曹锨。 經(jīng)常有香客問我孤个,道長,這世上最難降的妖魔是什么沛简? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任齐鲤,我火速辦了婚禮斥废,結果婚禮上,老公的妹妹穿的比我還像新娘给郊。我一直安慰自己牡肉,他們只是感情好,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布丑罪。 她就那樣靜靜地躺著荚板,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吩屹。 梳的紋絲不亂的頭發(fā)上跪另,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音煤搜,去河邊找鬼免绿。 笑死,一個胖子當著我的面吹牛擦盾,可吹牛的內(nèi)容都是我干的嘲驾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼迹卢,長吁一口氣:“原來是場噩夢啊……” “哼辽故!你這毒婦竟也來了?” 一聲冷哼從身側響起腐碱,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤誊垢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后症见,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喂走,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年谋作,在試婚紗的時候發(fā)現(xiàn)自己被綠了芋肠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡遵蚜,死狀恐怖帖池,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吭净,我是刑警寧澤睡汹,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站攒钳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雷滋。R本人自食惡果不足惜不撑,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一文兢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焕檬,春花似錦姆坚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腊敲,卻和暖如春击喂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碰辅。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工懂昂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人没宾。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓凌彬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親循衰。 傳聞我的和親對象是個殘疾皇子铲敛,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354