runtime Method Swizzling交換方法詳解

換了一家新公司钳榨,用OC寫的,前期還沒安排任務特恬,自己學一些相關(guān)知識

Method Swizzling原理:

用于改變一個已經(jīng)存在的 selector 實現(xiàn)庄撮。我們可以在程序運行時,通過改變 selector 所在 Class(類)的 method list(方法列表)的映射從而改變方法的調(diào)用戳粒。其實質(zhì)就是交換兩個方法的 IMP(方法實現(xiàn))
一般寫在Category里路狮,下面寫示例:

#import <UIKit/UIKit.h>
//這里是.h
NS_ASSUME_NONNULL_BEGIN

@interface UIImage (Category)
@property (nonatomic,strong) UIColor * defaultColor;


+ (UIImage *)jg_imageNamed:(NSString *)name;
+ (Class)jg_class;
@end

NS_ASSUME_NONNULL_END
//這里是.m
#import "UIImage+Category.h"
#import <objc/runtime.h>


@implementation UIImage (Category)

static char kDefaultColorKey;

@dynamic defaultColor;


- (void) setDefaultColor:(UIColor *)defaultColor {
    objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (UIColor *)defaultColor {
    return objc_getAssociatedObject(self, &kDefaultColorKey);
}

+(void)load{
    [super load];
    /* swizzling應該只在+load中完成。 在 Objective-C 的運行時中蔚约,每個類有兩個方法都會自動調(diào)用奄妨。+load 是在一個類被初始裝載時調(diào)用,+initialize 是在應用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的苹祟。兩個方法都是可選的砸抛,并且只有在方法被實現(xiàn)的情況下才會被調(diào)用。
     swizzling應該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態(tài)树枫,所以我們需要確保每個預防措施在運行時都是可用的直焙。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次砂轻。Grand Central Dispatch 的 dispatch_once滿足了所需要的需求奔誓,并且應該被當做使用swizzling 的初始化單例方法的標準。*/
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //當前類
        Class class = object_getClass((id)self);
        
        //原始類和替換類
        SEL originalSelector = @selector(class);
        SEL swizzleSelector = @selector(jg_class);
        
        //原始方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
        Method orginalMethod = class_getClassMethod(class, originalSelector);
        Method swizzleMethod = class_getClassMethod(class, swizzleSelector);
        
        /* 如果當前類沒有 原方法的 IMP搔涝,說明在從父類繼承過來的方法實現(xiàn)厨喂,
          需要在當前類中添加一個 originalSelector 方法,
          但是用 替換方法 swizzledMethod 去實現(xiàn)它
         */
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        if (didAddMethod) {
            // 原方法的 IMP 添加成功后庄呈,修改 替換方法的 IMP 為 原始方法的 IMP
            class_replaceMethod(class, swizzleSelector, method_getImplementation(orginalMethod), method_getTypeEncoding(orginalMethod));
        }else{
            // 添加失斖苫汀(說明已包含原方法的 IMP),調(diào)用交換兩個方法的實現(xiàn)
            method_exchangeImplementations(orginalMethod, swizzleMethod);
        }
    });
}



+(UIImage *)jg_imageNamed:(NSString *)name{
    UIImage *image = [UIImage jg_imageNamed:name];
    if (image) {
        NSLog(@"runtime添加額外功能---加載成功");
    }else{
        NSLog(@"runtime添加額外功能---加載失敗");
    }
    return image;
}

+ (Class)jg_class {
    NSLog(@"runtime Method Swizzling成功");
    Class c = [UIImage jg_class];
    return c;
}

@end

普通需要注意的點寫在的注釋里诬留,其他的再說一下
擴展類方法需要用

Method orginalMethod = class_getClassMethod(class, originalSelector);

擴展實例方法需要用

Method originalMethod = class_getInstanceMethod(class, originalSelector);

在如下代碼中幌绍,如果我們這么寫颁褂,且替換父類的方法比如UIImage父類的+(Class)class方法故响,可能會造成閃退

      Class class = object_getClass((id)self);
       //原始類和替換類
      SEL originalSelector = @selector(class);
      SEL swizzleSelector = @selector(jg_class);
        
      //原始方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
      Method orginalMethod = class_getClassMethod(class, originalSelector);
      Method swizzleMethod = class_getClassMethod(class, swizzleSelector);
      method_exchangeImplementations(orginalMethod, swizzleMethod);
     Class class = object_getClass((id)self);
      //原始類和替換類
     SEL originalSelector = @selector(class);
     SEL swizzleSelector = @selector(jg_class);

      //原始方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
     Method orginalMethod = class_getClassMethod(class, originalSelector);
     Method swizzleMethod = class_getClassMethod(class, swizzleSelector);

     BOOL didAddMethod = class_addMethod(class, originalSelector,method_getImplementation(swizzleMethod),method_getTypeEncoding(swizzleMethod));
     if (didAddMethod) {
       // 原方法的 IMP 添加成功后傀广,修改 替換方法的 IMP 為 原始方法的 IMP
          class_replaceMethod(class, swizzleSelector, method_getImplementation(orginalMethod),     method_getTypeEncoding(orginalMethod));
      }else{
      // 添加失敗(說明已包含原方法的 IMP)彩届,調(diào)用交換兩個方法的實現(xiàn)
          method_exchangeImplementations(orginalMethod, swizzleMethod);
      }

首先伪冰,父類調(diào)換用class方法會執(zhí)行子類中的jg_class方法實現(xiàn)。
然后又調(diào)用了jg_class方法樟蠕,但是贮聂,此時的調(diào)用者是NSObject對象,父類NSObject中并沒有jg_class方法實現(xiàn)寨辩。所以因方法找不到而報錯閃退吓懈。

在如下代碼中

+(UIImage *)jg_imageNamed:(NSString *)name{
    UIImage *image = [UIImage jg_imageNamed:name];
    if (image) {
        NSLog(@"runtime添加額外功能---加載成功");
    }else{
        NSLog(@"runtime添加額外功能---加載失敗");
    }
    return image;
}

+ (Class)jg_class {
    NSLog(@"runtime Method Swizzling成功");
    Class c = [UIImage jg_class];
    return c;
}

看樣子都有循環(huán)調(diào)用的代碼:

+(UIImage *)jg_imageNamed:(NSString *)name{
   UIImage *image = [UIImage jg_imageNamed:name];

+ (Class)jg_class {
    NSLog(@"runtime Method Swizzling成功");
    Class c = [UIImage jg_class];

但是為什么沒有遞歸調(diào)用導致閃退呢?
這是因為進行方法交換后靡狞,在執(zhí)行[UIImage imageNamed:name];時耻警,實際上找到的是jg_imageNamed:的方法實現(xiàn),而jg_imageNamed:方法實現(xiàn)中又執(zhí)行[UIImage jg_imageNamed:name];甸怕,同樣是因為方法交換甘穿,此時jg_imageNamed的方法實現(xiàn)也已經(jīng)指向了imageNamed:,所以并不會引起遞歸調(diào)用梢杭。相反温兼,如果我們在 jg_imageNamed方法中調(diào)用了[self imageNamed:name]才是會引起遞歸調(diào)用的,小伙伴們一定要注意N淦酢D寂小!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咒唆,一起剝皮案震驚了整個濱河市届垫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钧排,老刑警劉巖敦腔,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恨溜,居然都是意外死亡符衔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門糟袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來判族,“玉大人,你說我怎么就攤上這事项戴⌒伟铮” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辩撑。 經(jīng)常有香客問我界斜,道長,這世上最難降的妖魔是什么合冀? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任各薇,我火速辦了婚禮,結(jié)果婚禮上君躺,老公的妹妹穿的比我還像新娘峭判。我一直安慰自己,他們只是感情好棕叫,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布林螃。 她就那樣靜靜地躺著,像睡著了一般俺泣。 火紅的嫁衣襯著肌膚如雪疗认。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天砌滞,我揣著相機與錄音侮邀,去河邊找鬼。 笑死贝润,一個胖子當著我的面吹牛绊茧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播打掘,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼华畏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尊蚁?” 一聲冷哼從身側(cè)響起亡笑,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎横朋,沒想到半個月后仑乌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡琴锭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年晰甚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片决帖。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厕九,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出地回,到底是詐尸還是另有隱情扁远,我是刑警寧澤俊鱼,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站畅买,受9級特大地震影響并闲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜皮获,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一焙蚓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洒宝,春花似錦、人聲如沸萌京。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽知残。三九已至靠瞎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間求妹,已是汗流浹背乏盐。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留制恍,地道東北人父能。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像净神,于是被迫代替她去往敵國和親何吝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容