Runtime最全總結(jié)
本系列詳細(xì)講解Runtime知識(shí)點(diǎn),由于運(yùn)行時(shí)的內(nèi)容較多,所以將內(nèi)容拆分成以下幾個(gè)方面,可以自行選擇想要查看的部分
- OC運(yùn)行時(shí)機(jī)制Runtime(一):從isa指針開(kāi)始初步結(jié)識(shí)Runtime
- OC運(yùn)行時(shí)機(jī)制Runtime(二):探索Runtime的消息轉(zhuǎn)發(fā)機(jī)制和分類Category
- OC運(yùn)行時(shí)機(jī)制Runtime(三):關(guān)聯(lián)對(duì)象Associated Object和分類Category
- OC運(yùn)行時(shí)機(jī)制Runtime(四):嘗試使用黑魔法 Method Swizzling
本文主要分析Runtime黑魔法——Method Swizzling矿咕,接前兩篇文章詳細(xì)分析一下一些細(xì)節(jié)內(nèi)容。
Method Swizzling
通過(guò)前文分析Category狼钮,我們了解了OC在運(yùn)行時(shí)可以動(dòng)態(tài)添加方法碳柱,這種方式比起繼承的方式要輕量級(jí)許多,但是這種方式更適合新增某個(gè)方法熬芜,如果我們希望在原有基礎(chǔ)的方法上添加一定功能莲镣,那么繼承和分類的方式還是不大適合,這里我們會(huì)使用一個(gè)新的方法Method Swizzling
涎拉。
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
這是method結(jié)構(gòu)體的內(nèi)容瑞侮,主要SEL類型的方法名稱
以及IMP類型的函數(shù)指針,也就是方法的具體實(shí)現(xiàn)
鼓拧。那么當(dāng)我們修改IMP指針的指向
時(shí)半火,就相當(dāng)于修改了該方法的具體實(shí)現(xiàn)。
下面我們簡(jiǎn)單測(cè)試一下季俩。
Person * person = [Person new];
Method method1 = class_getInstanceMethod([Person class], @selector(speakAction));
Method method2 = class_getInstanceMethod([Person class], @selector(runAction));
method_exchangeImplementations(method1, method2);
[person speakAction];
[person runAction];
首先我們獲取兩個(gè)方法钮糖,使用method_exchangeImplementations
這個(gè)函數(shù)將兩個(gè)方法的IMP指針的指向做交換
,然后我們執(zhí)行這兩個(gè)方法酌住,看一下方法內(nèi)的打印結(jié)果藐鹤。
2019-04-16 14:15:31.014450+0800 Run
2019-04-16 14:15:31.014705+0800 Speak
這里能看到runAction方法首先被log出來(lái),說(shuō)明了兩個(gè)方法已經(jīng)做到了交換赂韵,我們找到函數(shù)的具體實(shí)現(xiàn)來(lái)徹底印證這個(gè)猜想。
void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
method_t *m1 = newmethod(m1_gen);
method_t *m2 = newmethod(m2_gen);
if (!m1 || !m2) return;
rwlock_write(&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;
rwlock_unlock_write(&runtimeLock);
return;
}
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
if (vtable_containsSelector(m1->name) ||
vtable_containsSelector(m2->name))
{
// Don't know the class - will be slow if vtables are affected
// fixme build list of classes whose Methods are known externally?
flushVtables(NULL);
}
// fixme catch NSObject changing to custom RR
// cls->setCustomRR();
// fixme update monomorphism if necessary
rwlock_unlock_write(&runtimeLock);
}
忽略嚴(yán)格處理部分挠蛉,可以看到m1的imp指針
和m2的imp指針
確實(shí)做到了交換祭示,以此實(shí)現(xiàn)交換了方法的具體實(shí)現(xiàn),下面用一個(gè)圖來(lái)表示這個(gè)操作谴古。
看得出Method Swizzling的原理并不很難理解质涛,使用起來(lái)也很便利,我們來(lái)想一個(gè)簡(jiǎn)單的需求:項(xiàng)目中很多時(shí)候我們可能用到ImageView展示圖片掰担,如果圖片展示不出我們不確定是否原因在于url字段問(wèn)題汇陆,那我們可以在添加url方法加上一段log,判斷字符串是否為空带饱,demo中我們使用本地圖片方式進(jìn)行測(cè)試毡代。
#import "UIImage+Detection.h"
@implementation UIImage (Detection)
+ (void)load {
Method originalMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method swappedMethod = class_getClassMethod([UIImage class], @selector(hy_imageNamed:));
method_exchangeImplementations(originalMethod, swappedMethod);
}
+ (UIImage *)hy_imageNamed:(NSString *)nameString {
if ([nameString isEqualToString:@""]) {
NSLog(@"missing image name");
}
return [UIImage hy_imageNamed:nameString];
}
@end
我們?yōu)閁IImage創(chuàng)建一個(gè)分類阅羹,創(chuàng)建一個(gè) hy_imageNamed
的方法,方法內(nèi)對(duì)imageName
字符串進(jìn)行判斷教寂,如果字符串為空我們進(jìn)行一個(gè)日志打印捏鱼,load
方法中我們把系統(tǒng)的imageNamed:
方法與我們的方法進(jìn)行交換,這樣一來(lái)酪耕,外部調(diào)用imageNamed
方法時(shí)导梆,實(shí)際是調(diào)用了我們的hy_imageNamed:
方法。
返回值雖然調(diào)用的是 hy_imageNamed 方法迂烁,但是由于有方法交換的原因所以這里是調(diào)用了原生 imageNamed 方法看尼,并沒(méi)有造成遞歸調(diào)用。
UIImage * image = [UIImage imageNamed:@""];
這樣一來(lái)控制臺(tái)會(huì)打印找不到圖片的日志信息盟步,滿足了我們的簡(jiǎn)單需求藏斩,項(xiàng)目中我們可以自由發(fā)揮,不過(guò)也不能濫用址芯,以防給后期代碼維護(hù)造成困難灾茁。
總結(jié)
方法交換的實(shí)質(zhì)就是通過(guò)交換IMP函數(shù)指針的指向
,來(lái)交換方法的具體實(shí)現(xiàn)
谷炸,我們可以將Method Swizzling
這段代碼寫到任何一處北专,但是只有執(zhí)行了這段代碼之后才可以實(shí)現(xiàn)交換。
最后
Runtime相關(guān)還有很多知識(shí)點(diǎn)旬陡,例如kvo的原理拓颓,weak的實(shí)現(xiàn)等等,但是作者文筆有限描孟,深層問(wèn)題還在探索中驶睦,希望可以在源碼角度解析而不是很淺層的分析,歡迎大家一起討論匿醒,更歡迎大牛們給一些意見(jiàn)或建議场航,如果覺(jué)得本文對(duì)您有些作用,請(qǐng)?jiān)谙路近c(diǎn)個(gè)贊再走哈~