介紹
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)志位