Method Swizzling

介紹

Method Swizzling 是 OC 非常實(shí)用的特性,它可以動態(tài)的替換2個方法的實(shí)現(xiàn)蜀踏,是面向切面編程的一種手段,舉個比較經(jīng)典的例子:
如果想為 APP 中的每個界面添加訪問統(tǒng)計镰绎,這個時候可以為每個 VC 的 viewWillAppear 方法添加統(tǒng)計代碼脓斩,傳統(tǒng)的做法是寫一個基類,然后讓所有的 VC 都繼承此基類畴栖,從而都有了統(tǒng)計的功能随静,即使如此也不得不修改原有的類的代碼,并且如果是第三方提供的 VC 也不能添加統(tǒng)計的功能吗讶。如果是 Method Swizzling 的手段燎猛,我們可以在 runtime 的時候動態(tài)替換 viewWillAppear 的方法,首先實(shí)現(xiàn)一個可以統(tǒng)計的 my_viewWillAppear 方法照皆,之后就可以使用 Method Swizzling 來替換 UIKit 提供的 viewWillAppear 從而實(shí)現(xiàn)任意界面添加打點(diǎn)代碼重绷,并且不需要修改一行代碼。

使用

使用 Method Swizzling 一般使用一下的格式

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(xxxx:);
        SEL swizzledSelector = @selector(xxx_xxxx:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

我們一起來看看是如何工作的

應(yīng)該在 + load 中使用

+load 是在一個類被初始裝載時調(diào)用膜毁,+initialize 是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的昭卓,如果一個類從沒被調(diào)用,那么它的 initialize 永遠(yuǎn)不會被調(diào)用瘟滨,所以我們應(yīng)該在 + load 中 Method Swizzling候醒。

用 dispatch_once 來包裹

我們應(yīng)該始終保證 Method Swizzling 只會進(jìn)行一次,使用 dispatch_once 來保證唯一性

替換

Class class = [self class];

        SEL originalSelector = @selector(xxxx:);
        SEL swizzledSelector = @selector(xxx_xxxx:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

首先先獲取到 method杂瘸。

  BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

判斷原方法時候有存在倒淫,如果是存在的那么 didAddMethod 將會是 NO,如果沒存在則會優(yōu)先添加败玉。

class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));

如果是沒存在的狀態(tài)敌土,那么 originalSelector 的實(shí)現(xiàn)會是 swizzledSelector 的實(shí)現(xiàn),所以我們這邊只需要將 swizzledSelector 的實(shí)現(xiàn)替換成 originalSelector 實(shí)現(xiàn)即可运翼。

method_exchangeImplementations(originalMethod, swizzledMethod);

如果是已經(jīng)存在的方法返干,可以使用 method_exchangeImplementations 來交換2個方法的實(shí)現(xiàn)。

源碼下的 Method Swizzling

Method Swizzling 主要使用了3個 runtime 方法血淌,分別是 class_addMethod矩欠,class_replaceMethod,method_exchangeImplementations

class_addMethod

此函數(shù)的功能是為類動態(tài)的添加一個方法,底層調(diào)用了 addMethod 方法晚顷,并且將最后一個參數(shù)設(shè)置為 NO。

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    rwlock_writer_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

在 addMethod 中 最好一個參數(shù)表示的是在添加一個已經(jīng)存在的方法實(shí)現(xiàn)情況下時候需要替換這個方法的實(shí)現(xiàn)疗疟,顯然 class_addMethod 將此參數(shù)設(shè)置為 NO,默認(rèn)不進(jìn)行替換该默。

addMethod 的實(shí)現(xiàn)思路為:
首先會尋找 class 中是否有需要添加 sel 的實(shí)現(xiàn)(IMP),如果找到策彤,那么會根據(jù) replace 參數(shù)來判斷時候進(jìn)行替換栓袖,然后將此 IMP 返回。
如果沒找到店诗,會為這個類的方法列表中添加一個 method裹刮,也就是添加一個方法。

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertWriting();

    assert(types);
    assert(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdup(types);
        if (!ignoreSelector(name)) {
            newlist->first.imp = imp;
        } else {
            newlist->first.imp = (IMP)&_objc_ignored_method;
        }

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

class_replaceMethod

class_replaceMethod 底層也是調(diào)用了 addMethod 方法庞瘸,并且將最后一個參數(shù)設(shè)置為 YES捧弃。來表示即使原方法存在,那么就去替換此方法的實(shí)現(xiàn)(IMP)

IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

    rwlock_writer_t lock(runtimeLock);
    return addMethod(cls, name, imp, types ?: "", YES);
}

method_exchangeImplementations

此方法的功能是交換2個方法的實(shí)現(xiàn)(IMP)

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

    rwlock_writer_t lock(runtimeLock);

    if (ignoreSelector(m1->name)  ||  ignoreSelector(m2->name)) {
        // Ignored methods stay ignored. Now they're both ignored.
        m1->imp = (IMP)&_objc_ignored_method;
        m2->imp = (IMP)&_objc_ignored_method;
        return;
    }

    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);

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

首先判斷方法是否是 ignoreSelector 擦囊,ignoreSelector 分別為 retain违霞,release,autorelease 等系統(tǒng)關(guān)鍵方法被交換導(dǎo)致異常瞬场。
然后交換2個方法的 imp 指針來達(dá)到交換方法的實(shí)現(xiàn)

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

之后更新緩存和更新一下 RR/AWZ 標(biāo)志位

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末买鸽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贯被,更是在濱河造成了極大的恐慌眼五,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彤灶,死亡現(xiàn)場離奇詭異看幼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)枢希,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門桌吃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苞轿,你說我怎么就攤上這事茅诱。” “怎么了搬卒?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵瑟俭,是天一觀的道長。 經(jīng)常有香客問我契邀,道長摆寄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮微饥,結(jié)果婚禮上逗扒,老公的妹妹穿的比我還像新娘。我一直安慰自己欠橘,他們只是感情好矩肩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肃续,像睡著了一般黍檩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上始锚,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天刽酱,我揣著相機(jī)與錄音,去河邊找鬼瞧捌。 笑死棵里,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姐呐。 我是一名探鬼主播衍慎,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼皮钠!你這毒婦竟也來了稳捆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤麦轰,失蹤者是張志新(化名)和其女友劉穎乔夯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體款侵,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡末荐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了新锈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甲脏。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妹笆,靈堂內(nèi)的尸體忽然破棺而出块请,到底是詐尸還是另有隱情,我是刑警寧澤拳缠,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布墩新,位于F島的核電站,受9級特大地震影響窟坐,放射性物質(zhì)發(fā)生泄漏海渊。R本人自食惡果不足惜绵疲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臣疑。 院中可真熱鬧盔憨,春花似錦、人聲如沸讯沈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芙盘。三九已至,卻和暖如春脸秽,著一層夾襖步出監(jiān)牢的瞬間儒老,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工记餐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驮樊,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓片酝,卻偏偏與公主長得像囚衔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雕沿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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