前言
剛才翻代碼時(shí)發(fā)現(xiàn)N年前寫的方法交換缅叠,當(dāng)時(shí)方法交換還是個(gè)新奇的東東厉颤,網(wǎng)上找了一番發(fā)現(xiàn)都有各種問(wèn)題,于是動(dòng)手寫了一個(gè)渤滞。如今方法交換的寫法已經(jīng)爛大街了,但把我當(dāng)時(shí)寫的拿出來(lái)一對(duì)比榴嗅,不得不自吹一句妄呕,還是我自己寫的更優(yōu)雅。
上代碼
我當(dāng)年的實(shí)現(xiàn)代碼
static void exchangeSelector(Class oClass, SEL oSelector, Class sClass, SEL sSelector) {
Method originalMethod = class_getInstanceMethod(oClass, oSelector);
Method swizzledMethod = class_getInstanceMethod(sClass, sSelector);
IMP oIMP = method_getImplementation(originalMethod);
IMP sIMP = method_getImplementation(swizzledMethod);
const char *oType = method_getTypeEncoding(originalMethod);
const char *sType = method_getTypeEncoding(swizzledMethod);
class_replaceMethod(oClass, oSelector, sIMP, sType);
class_replaceMethod(oClass, sSelector, oIMP, oType);
}
與某流行的代碼對(duì)比
放在一起看嗽测,單論顏值就不是一個(gè)檔次的
分析
目的
我們使用方法交換的目的實(shí)際是修改方法绪励,當(dāng)然還要保持原方法的實(shí)現(xiàn)可被利用肿孵。
如圖:新方法的實(shí)現(xiàn)和原方法交換后,通過(guò)新方法名就可以使用原本的實(shí)現(xiàn)疏魏。
問(wèn)題
而出現(xiàn)的BUG停做,通常是原方法的實(shí)現(xiàn)并不在類本身,而是在父類中大莫,替換的時(shí)候影響到了父類
我們想要的結(jié)果
處理過(guò)程
理論處理
大象裝冰箱分幾步蛉腌?1.獲取大象,2.放進(jìn)冰箱只厘。
我們?nèi)绾谓粨Q變量呢烙丛?1.獲取舊值,2.放進(jìn)新變量羔味。下面的寫法最容易理解了吧河咽。
{
//交換a, b兩個(gè)變量的值;
tmp1 = a;
tmp2 = b;
a = tmp2;
b = tmp1;
}
接下來(lái)是交換方法,我們?nèi)绾谓粨Q方法呢介评?1.獲取舊實(shí)現(xiàn)库北,2.放進(jìn)新方法爬舰。
把class比喻成字典们陆,把方法名比喻成key,把方法實(shí)現(xiàn)比喻成value情屹,把class有父類比喻字典也像NSUserDefaults一樣有更深的域坪仇,那么我們要做的事情就是這個(gè)
exchangeSelector(funA, funB) {
class = self.class;
methodA = class[funA];
methodB = class[funB];
class[funA] = methodB;
class[funB] = methodA;
}
再?gòu)?fù)雜點(diǎn),methodB 沒(méi)必要限定在本類垃你,畢竟要加一堆類別也挺煩的椅文,而且有些類是隱藏的,強(qiáng)行聲明出來(lái)再加類別擴(kuò)展方法總感覺(jué)很不靠譜惜颇。于是我們指定 methodB 皆刺,由于methodB的獲取不是常用方法,我們換成把獲取 methodB 所需的條件傳入
exchangeSelector(class, funA, classForMethodB, funForMethodB) {
methodA = class[funA];
methodB = classForMethodB[funForMethodB];
class[funA] = methodB;
class[funB] = methodA;
}
我當(dāng)年的處理
就是按上面最單純的過(guò)程凌摄,換成最直白的代碼羡蛾。
- 獲取實(shí)現(xiàn)
//原實(shí)現(xiàn)
Method originalMethod = class_getInstanceMethod(oClass, oSelector);
IMP oIMP = method_getImplementation(originalMethod);
const char *oType = method_getTypeEncoding(originalMethod);
//新實(shí)現(xiàn)
Method swizzledMethod = class_getInstanceMethod(sClass, sSelector);
IMP sIMP = method_getImplementation(swizzledMethod);
const char *sType = method_getTypeEncoding(swizzledMethod);
- 把實(shí)現(xiàn)放進(jìn)方法。
//把新實(shí)現(xiàn)放進(jìn)原方法
class_replaceMethod(oClass, oSelector, sIMP, sType);
//把原實(shí)現(xiàn)放進(jìn)新方法锨亏。這里要注意痴怨,我們的目的只是替換原本類里的方法。
class_replaceMethod(oClass, sSelector, oIMP, oType);
什么器予?你問(wèn)上面那個(gè)BUG的問(wèn)題浪藻,原方法子類里沒(méi)實(shí)現(xiàn),是在父類實(shí)現(xiàn)的怎么辦乾翔?
如果讓你給一個(gè)變量賦值爱葵,你是這樣做
int a; //假如有一個(gè)變量a,我們要給它賦值
if (a) { //先判斷a原本是否有值
a = 1; //如果a已經(jīng)有值了,就將a的舊值改成新值
} else {
a = 1; //如果a沒(méi)值,則直接使用新值
}
還是這樣做
int a; //假如有一個(gè)變量a,我們要給它賦值
a = 1; //不判斷a當(dāng)前是否有值萌丈,直接賦值
我選擇后者暇韧。
如果你來(lái)設(shè)計(jì)一個(gè)函數(shù),用來(lái)設(shè)置字典中某個(gè)key所對(duì)應(yīng)的值浓瞪,你會(huì)設(shè)計(jì)成當(dāng)別人使用時(shí)懈玻,需要像左邊這樣先判斷當(dāng)前是否有值,再根據(jù)不同的情況進(jìn)行不同的處理乾颁,還是向右邊這樣一步搞定涂乌?
我還是會(huì)選擇后者,我問(wèn)過(guò)別人英岭,非常巧合的是他們也選擇后者湾盒,更巧合的是 class_replaceMethod 也和我們一樣選擇了后者。所以我們根本不用考慮子類里有沒(méi)有實(shí)現(xiàn)這種事情诅妹。
某流行處理
不多說(shuō)了罚勾,除了 class_replaceMethod 還要了解 class_addMethod、method_exchangeImplementations 吭狡,然后再做判斷尖殃,不同分支做不同處理,看起來(lái)就容易令人迷惑的樣子划煮。