在實(shí)際開(kāi)發(fā)場(chǎng)景中,有時(shí)候我們需要在調(diào)用系統(tǒng)方法察净,或者某個(gè)類的方法的時(shí)候锈至,增加自己的一些邏輯操作,這時(shí)候可以采用 方法交換 的方式去實(shí)現(xiàn)這個(gè)需求们拙。這種方式也被稱為 黑魔法(Method swizzling)或者 hook突勇,網(wǎng)上也有很多這方面的文檔解釋埂奈,在這里主要是記錄一下,hook
的時(shí)候遇到的問(wèn)題。
場(chǎng)景一:對(duì)某個(gè)類自身的方法進(jìn)行 hook 操作
什么意思呢借宵?舉個(gè)例子,NSString
這個(gè)類欲间,有一個(gè) substringToIndex:
方法,這個(gè)方法是在 NSString+NSStringExtensionMethods
這樣的一個(gè)分類里面达址。
需求:在使用 substringToIndex:
方法的時(shí)候,希望能在里面增加一些邏輯判斷苛败,比如判斷當(dāng)前傳入的 index
是否在當(dāng)前字符串范圍之內(nèi)。
NSString *string = @"abcd";
NSLog(@"%@", [string substringToIndex:10]);
這里傳入的 10乳蛾,字符串沒(méi)有這么長(zhǎng)的長(zhǎng)度,如果直接使用系統(tǒng)的方法,程序運(yùn)行起來(lái)蹦魔,立馬發(fā)生閃退招盲。
下面,進(jìn)行 hook
操作
-
給
NSString
新建一個(gè)分類,并在 load 方法中進(jìn)行hook
操作+ (void)load { // 系統(tǒng)方法 Method system_method = class_getInstanceMethod([self class], @selector(substringToIndex:)); // 將要替換系統(tǒng)方法 Method my_method = class_getInstanceMethod([self class], @selector(yxc_substringToIndex:)); // 進(jìn)行交換 method_exchangeImplementations(system_method, my_method); } - (NSString *)yxc_substringToIndex:(NSUInteger)to { // 判斷傳入的數(shù)值是否大于當(dāng)前字符串的范圍设拟,如果大于的話,取當(dāng)前字符串的最大長(zhǎng)度 if (to > self.length) { to = self.length; } return [self yxc_substringToIndex:to]; }
這樣就
hook
完成了,查看結(jié)果:
這樣看起來(lái)牢硅,hook
操作很簡(jiǎn)單,沒(méi)有什么問(wèn)題如筛,但是這只是一種情況擦剑。
場(chǎng)景二:對(duì)某個(gè)類的父類或者基類的方法進(jìn)行 hook 操作
下面,對(duì) init
這個(gè)方法進(jìn)行 hook
操作。
-
因?yàn)?
NSString
特殊性肉康,在這里不再用NSString
進(jìn)行舉例了骑素,新建一個(gè)Person
類侠姑,繼承于NSObject
邦邦;再給Person
類創(chuàng)建一個(gè)分類,然后按照上面的方式對(duì)Person
的init
方法進(jìn)行hook
。+ (void)load { Class cls = [self class]; Method system_method = class_getInstanceMethod(cls, @selector(init)); Method my_method = class_getInstanceMethod(cls, @selector(yxc_init)); method_exchangeImplementations(system_method, my_method); } - (instancetype)yxc_init { NSLog(@"%s", __func__); return [self yxc_init]; }
通過(guò)
alloc
和init
創(chuàng)建一個(gè)Person
對(duì)象,并未出現(xiàn)異常。-
緊接著創(chuàng)建一個(gè)
NSObject
對(duì)象捉偏,這時(shí)候問(wèn)題出現(xiàn)了讹躯,程序進(jìn)入死循環(huán),并且報(bào)yxc_init:
方法找不到。分析:
-
init
方法并不是Person
類本身的實(shí)例(對(duì)象)方法,而是父類NSObject
的方法绕娘。由于Person
本身沒(méi)有該方法升酣,所以class_getInstanceMethod
獲取到的方法是通過(guò)Person
的superclass
指針從NSObject
類中獲取到了init
這個(gè)方法复颈。 -
method_exchangeImplementations
操作將NSObject
的init
方法的實(shí)現(xiàn)與Person
類的yxc_init
方法的實(shí)現(xiàn)進(jìn)行互換了帜讲,這時(shí)候調(diào)用init
方法實(shí)際上是調(diào)用了yxc_init
方法。 - 創(chuàng)建一個(gè)
Person
對(duì)象時(shí)玷氏,調(diào)用init
方法赞辩,運(yùn)行時(shí)會(huì)去查找yxc_init
的實(shí)現(xiàn)想诅,因?yàn)?yxc_init
方法是Person
自身的方法忘古,所以查找到了直接調(diào)用娘荡。(消息發(fā)送機(jī)制) - 而創(chuàng)建一個(gè)
NSObject
對(duì)象時(shí),調(diào)用init
方法,運(yùn)行時(shí)去查找yxc_init
方法的時(shí)候,NSObject
是沒(méi)有這個(gè)方法,這個(gè)方法存在于Person
類中柏蘑,所以查找完畢庞溜,還是找不到這個(gè)方法漫试,就拋異常了普泡。
-
正確的 hook
做法是砰嘁,先將 init
方法添加到 Person
類中口糕,如果這個(gè)類當(dāng)前有這個(gè)方法(而不是父類)孤里,則不添加,直接 exchange
,否則添加了 init
方法,然后再將 yxc_init
方法的實(shí)現(xiàn)設(shè)置成 init
方法的實(shí)現(xiàn)。
+ (void)load {
Class cls = [self class];
// 1. 獲取到父類的 init 方法
Method system_method = class_getInstanceMethod(cls, @selector(init));
// 2. 獲取到當(dāng)前類的 yxc_init 方法
Method my_method = class_getInstanceMethod(cls, @selector(yxc_init));
// 3. 先將 init 方法添加到當(dāng)前類中,并且將 yxc_init 作為 init 方法的實(shí)現(xiàn)
BOOL addSuccess = class_addMethod(cls,
@selector(init),
method_getImplementation(my_method),
method_getTypeEncoding(my_method));
// 4. 判斷 init 添加到當(dāng)前類中是否成功
if (addSuccess) {
// 4.1 方法添加成功,則意味著當(dāng)前類在添加之前并沒(méi)有 init 方法,添加成功后就進(jìn)行方法替換,將 init 方法的實(shí)現(xiàn)替換成 yxc_init 方法的實(shí)現(xiàn)
class_replaceMethod(cls,
@selector(yxc_init),
method_getImplementation(system_method),
method_getTypeEncoding(system_method));
} else {
// 4.2 方法添加失敗,說(shuō)明當(dāng)前類已存在該方法,直接進(jìn)行方法交換
method_exchangeImplementations(system_method, my_method);
}
}
- (instancetype)yxc_init {
NSLog(@"%s", __func__);
return [self yxc_init];
}
運(yùn)行結(jié)果顯示:
通過(guò)這樣的方式進(jìn)行對(duì) 父類或者基類 方法的 hook
患整,最終沒(méi)有發(fā)現(xiàn)其他異常,以此記錄父阻。
最后封裝一下 hook 邏輯操作
/// hook 方法
/// @param cls 類
/// @param originSelector 將要 hook 掉的方法
/// @param swizzledSelector 新的方法
/// @param clsMethod 類方法
+ (void)hookMethod:(Class)cls originSelector:(SEL)originSelector swizzledSelector:(SEL)swizzledSelector classMethod:(BOOL)clsMethod {
Method origin_method;
Method swizzled_method;
if (clsMethod) {
// 類方法
origin_method = class_getClassMethod(cls, originSelector);
swizzled_method = class_getClassMethod(cls, swizzledSelector);
} else {
// 實(shí)例(對(duì)象)方法
origin_method = class_getInstanceMethod(cls, originSelector);
swizzled_method = class_getInstanceMethod(cls, swizzledSelector);
}
BOOL addSuccess = class_addMethod(cls,
originSelector,
method_getImplementation(swizzled_method),
method_getTypeEncoding(swizzled_method)
);
if (addSuccess) {
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(origin_method),
method_getTypeEncoding(origin_method)
);
} else {
method_exchangeImplementations(origin_method, swizzled_method);
}
}
類簇(Class Clusters)
Class Clusters
(類簇)是抽象工廠模式在iOS下的一種實(shí)現(xiàn)斟览,眾多常用類妓羊,如 NSString
,NSArray
硅则,NSDictionary
穷吮,NSNumber
都運(yùn)作在這一模式下,它是接口簡(jiǎn)單性和擴(kuò)展性的權(quán)衡體現(xiàn)缠诅,在我們完全不知情的情況下褥伴,偷偷隱藏了很多具體的實(shí)現(xiàn)類逊躁,只暴露出簡(jiǎn)單的接口。
下面對(duì) NSArray
進(jìn)行類簇講解
系統(tǒng)會(huì)創(chuàng)建 __NSPlaceholderArray
假勿、 __NSSingleObjectArrayI
邦泄、 __NSArray0
诚亚、 __NSArrayM
等一些類簇夷家,下面對(duì)這些類簇進(jìn)行 hook
操作
+ (void)load {
[self hookOriginClass:NSClassFromString(@"__NSPlaceholderArray") currentClass:[NSArray class] originSelector:@selector(initWithObjects:count:) swizzledSelector:@selector(yxc_initWithObjects:count:) classMethod:NO];
[self hookOriginClass:NSClassFromString(@"__NSSingleObjectArrayI") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex:) classMethod:NO];
[self hookOriginClass:NSClassFromString(@"__NSArray0") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex1:) classMethod:NO];
[self hookOriginClass:NSClassFromString(@"__NSArrayM") currentClass:[NSArray class] originSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(yxc_objectAtIndexedSubscript:) classMethod:NO];
}
這樣就對(duì)數(shù)組中的一些方法進(jìn)行 hook 完了,而且也并沒(méi)有什么問(wèn)題察迟。
到這里概荷,就有一個(gè)疑問(wèn):在這里替換同一個(gè) SEL
為 objectAtIndex:
慈鸠,而這個(gè)方法是屬于 NSArray
這個(gè)類缕题,為什么這里替換了兩次,彼此都沒(méi)有影響到,按理來(lái)說(shuō)根據(jù)同一個(gè) SEL
獲取到的 IMP
進(jìn)行 replace
或者 exchange
姿搜,那么最后生效的應(yīng)該是最后一次進(jìn)行 hook
的方法實(shí)現(xiàn),但是經(jīng)過(guò)發(fā)現(xiàn)锰提,沒(méi)有受影響。
首先類簇是需要繼承于原來(lái)那個(gè)類,在原來(lái)那個(gè)類的基礎(chǔ)上衍生了許多類出來(lái),下面我們用代碼證明這一點(diǎn)呻右。
Class __NSArrayM = NSClassFromString(@"__NSArrayM");
Class __NSArray0 = NSClassFromString(@"__NSArray0");
Class __NSSingleObjectArrayI = NSClassFromString(@"__NSSingleObjectArrayI");
Class __NSPlaceholderArray = NSClassFromString(@"__NSPlaceholderArray");
NSLog(@"__NSArrayM -> superclass : %@", class_getSuperclass(__NSArrayM));
NSLog(@"__NSArray0 -> superclass : %@", class_getSuperclass(__NSArray0));
NSLog(@"__NSSingleObjectArrayI -> superclass : %@", class_getSuperclass(__NSSingleObjectArrayI));
NSLog(@"__NSPlaceholderArray -> superclass : %@", class_getSuperclass(__NSPlaceholderArray));
輸出結(jié)果:
既然 SEL 是 NSArray 的方法罐韩,為什么在 hook 的時(shí)候,能 hook 到每個(gè)類簇對(duì)應(yīng)的想法?
猜想:是不是每個(gè)類簇州藕,都實(shí)現(xiàn)了 objectAtIndex:
這個(gè)方法待牵,導(dǎo)致根據(jù) SEL
獲取到方法實(shí)現(xiàn)是不相同的
下面進(jìn)行驗(yàn)證這個(gè)猜想
+ (void)load {
[self hookOriginClass:NSClassFromString(@"__NSPlaceholderArray") currentClass:[NSArray class] originSelector:@selector(initWithObjects:count:) swizzledSelector:@selector(yxc_initWithObjects:count:) classMethod:NO];
NSLog(@"交換前");
[self logInfo];
[self hookOriginClass:NSClassFromString(@"__NSSingleObjectArrayI") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex:) classMethod:NO];
NSLog(@"__NSSingleObjectArrayI交換后");
[self logInfo];
[self hookOriginClass:NSClassFromString(@"__NSArray0") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex1:) classMethod:NO];
NSLog(@"__NSArray0交換后");
[self logInfo];
[self hookOriginClass:NSClassFromString(@"__NSArrayM") currentClass:[NSArray class] originSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(yxc_objectAtIndexedSubscript:) classMethod:NO];
}
+ (void)logInfo {
Class singleObjectCls = NSClassFromString(@"__NSSingleObjectArrayI");
Class __NSArray0Cls = NSClassFromString(@"__NSArray0");
Class currentCls = [self class];
SEL selector = @selector(objectAtIndex:);
Method singleObjectClsMethod = class_getInstanceMethod(singleObjectCls, selector);
Method __NSArray0ClsMethod = class_getInstanceMethod(__NSArray0Cls, selector);
Method currentMethod = class_getInstanceMethod(currentCls, selector);
IMP singleObjectClsMethodIMP = method_getImplementation(singleObjectClsMethod);
IMP __NSArray0ClsMethodIMP = method_getImplementation(__NSArray0ClsMethod);
IMP currentIMP = method_getImplementation(currentMethod);
NSLog(@"selector : %p, singleObjectClsMethod : %p, __NSArray0ClsMethod : %p, currentMethod : %p, singleObjectClsMethodIMP : %p, __NSArray0ClsMethodIMP : %p, currentIMP : %p",
selector, singleObjectClsMethod, __NSArray0ClsMethod, currentMethod, singleObjectClsMethodIMP, __NSArray0ClsMethodIMP, currentIMP);
}
以上代碼踢俄,在 hook
objectAtIndex:
方法之前和 hook
完一個(gè)、兩個(gè)之后對(duì) SEL
晴及、class
都办、Method
、IMP
信息輸出
2020-11-02 20:02:42.598040+0800 Block[32615:646190] 交換前==================
2020-11-02 20:02:42.598612+0800 Block[32615:646190] selector : 0x7fff7256d44e, singleObjectClsMethod : 0x7fff85fe75c0, __NSArray0ClsMethod : 0x7fff85fcd260, currentMethod : 0x7fff85fda938, singleObjectClsMethodIMP : 0x7fff2e31daf6, __NSArray0ClsMethodIMP : 0x7fff2e41e13b, currentIMP : 0x7fff2e4629fe
2020-11-02 20:02:42.598878+0800 Block[32615:646190] __NSSingleObjectArrayI交換后======================
2020-11-02 20:02:42.598970+0800 Block[32615:646190] selector : 0x7fff7256d44e, singleObjectClsMethod : 0x7fff85fe75c0, __NSArray0ClsMethod : 0x7fff85fcd260, currentMethod : 0x7fff85fda938, singleObjectClsMethodIMP : 0x100003750, __NSArray0ClsMethodIMP : 0x7fff2e41e13b, currentIMP : 0x7fff2e4629fe
2020-11-02 20:02:42.599166+0800 Block[32615:646190] __NSArray0交換后===================
2020-11-02 20:02:42.599275+0800 Block[32615:646190] selector : 0x7fff7256d44e, singleObjectClsMethod : 0x7fff85fe75c0, __NSArray0ClsMethod : 0x7fff85fcd260, currentMethod : 0x7fff85fda938, singleObjectClsMethodIMP : 0x100003750, __NSArray0ClsMethodIMP : 0x1000037d0, currentIMP : 0x7fff2e4629fe
根據(jù)輸出的地址,可以看出根據(jù)不同的類簇獲取到的 Method 的方法結(jié)構(gòu)體地址也是不同一個(gè)琳钉,還有方法實(shí)現(xiàn)的地址也是不同一塊存儲(chǔ)空間势木,那就證明了猜想,根據(jù) SEL 獲取到的 Method 和 IMP 不同一個(gè)歌懒,可能是在每個(gè)類簇內(nèi)部對(duì)父類NSArray 的 objectAtIndex:
重新實(shí)現(xiàn)了一下啦桌,導(dǎo)致獲取到的并不是同一個(gè)。
為了驗(yàn)證是否子類重寫了父類的方法獲取到的并不是同一個(gè)(原理來(lái)講是不同一個(gè)的及皂,下面用代碼來(lái)驗(yàn)證這個(gè)想法)
新建一個(gè) Person
類甫男,并且聲明一個(gè) test
對(duì)象方法并實(shí)現(xiàn),然后創(chuàng)建一個(gè) Student
類验烧,繼承于 Person
類板驳,先不重寫父類的 test
方法。
Student
未重寫父類 Person
的 test
方法碍拆,通過(guò)各自獲取到的 Method
和 IMP
的地址都是同一個(gè)
下面 Student
進(jìn)行重寫 test
方法
這時(shí)候發(fā)現(xiàn)若治,通過(guò)各自獲取 Method
和 IMP
的地址已經(jīng)不一樣了
這就驗(yàn)證了以上的猜想,在類簇內(nèi)部中感混,會(huì)對(duì)父類的一些方法進(jìn)行重寫端幼。這就導(dǎo)致可能某一個(gè)方法,在一個(gè)類簇中已經(jīng)進(jìn)行了 hook弧满,但是可能還是會(huì)出現(xiàn)方法名相同婆跑,但是類名不一樣的方法報(bào)錯(cuò),就像上面的 objectAtIndex:
方法一樣谱秽,如果只是對(duì) __NSSingleObjectArrayI
進(jìn)行了替換或者交換方法操作洽蛀,但是并沒(méi)有對(duì) __NSArray0
進(jìn)行同樣的操作,那么還是會(huì)出現(xiàn)索引超出界面疟赊,沒(méi)有達(dá)到預(yù)防的效果郊供。
class_addMethod
函數(shù)官方文檔描述
/**
* Adds a new method to a class with a given name and implementation.
*
* @param cls The class to which to add a method.
* @param name A selector that specifies the name of the method being added.
* @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
* @param types An array of characters that describe the types of the arguments to the method.
*
* @return YES if the method was added successfully, otherwise NO
* (for example, the class already contains a method implementation with that name).
*
* @note class_addMethod will add an override of a superclass's implementation,
* but will not replace an existing implementation in this class.
* To change an existing implementation, use method_setImplementation.
*/
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
下面對(duì) class_addMethod
進(jìn)行源碼分析
/// cls 類名
/// name 方法名
/// imp 方法實(shí)現(xiàn)
/// types 方法簽名
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
// 沒(méi)有傳入 類名 直接返回 NO
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
// 開(kāi)始添加方法,對(duì)返回的結(jié)果進(jìn)行取反近哟,這里返回的是一個(gè) IMP 類型的結(jié)果
return ! addMethod(cls, name, imp, types ?: "", NO);
}
/// cls 類名
/// name 方法名
/// imp 方法實(shí)現(xiàn)
/// types 方法簽名
/// replace 是否直接替換驮审,這里傳入的是 NO
static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
ASSERT(types);
ASSERT(cls->isRealized());
method_t *m;
// 查找該方法
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists 已經(jīng)存在該方法
if (!replace) {
// 當(dāng) replace 為 NO 時(shí),直接返回該方法的實(shí)現(xiàn)
result = m->imp;
} else {
// 當(dāng) replace 為 YES 時(shí)吉执,通過(guò) _method_setImplementation疯淫,直接將方法進(jìn)行替換
result = _method_setImplementation(cls, m, imp);
}
} else {
// 該方法不存在,對(duì)傳入的類進(jìn)行動(dòng)態(tài)添加方法
auto rwe = cls->data()->extAllocIfNeeded();
// fixme optimize
// 創(chuàng)建一個(gè)方法列表
method_list_t *newlist;
// 分配內(nèi)存戳玫,并設(shè)置好 method_list_t 的值
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 = strdupIfMutable(types);
newlist->first.imp = imp;
// 準(zhǔn)備方法合并到該類中
prepareMethodLists(cls, &newlist, 1, NO, NO);
// 開(kāi)始合并
rwe->methods.attachLists(&newlist, 1);
flushCaches(cls);
result = nil;
}
return result;
}
/// cls 類名
/// sel 方法名
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
// for 循環(huán)遍歷熙掺,根據(jù)傳入的 sel 方法進(jìn)行查找當(dāng)前類是否有該方法
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
// 查找傳入的方法列表是否有 sel 方法
method_t *m = search_method_list_inline(*mlists, sel);
// 找到了返回
if (m) return m;
}
return nil;
}
ALWAYS_INLINE static method_t *search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
// 根據(jù)不同方式進(jìn)行查找
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
// 有序查找
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
// 無(wú)序查找
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
// 二分查找方法
ALWAYS_INLINE static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
在使用 class_addMethod
添加方法時(shí),只會(huì)在當(dāng)前的類進(jìn)行查找方法咕宿,并不會(huì)像 消息機(jī)制
那樣在當(dāng)前類找不到币绩,就去父類查找蜡秽。在當(dāng)前類查找不到,就在當(dāng)前類動(dòng)態(tài)添加方法并設(shè)置實(shí)現(xiàn)缆镣;如果查找到了就不做操作芽突,返回查找到的方法實(shí)現(xiàn),然后通過(guò)取反操作董瞻,返回添加結(jié)果寞蚌。
class_replaceMethod
函數(shù)官方文檔描述
/**
* Replaces the implementation of a method for a given class.
*
* @param cls The class you want to modify.
* @param name A selector that identifies the method whose implementation you want to replace.
* @param imp The new implementation for the method identified by name for the class identified by cls.
* @param types An array of characters that describe the types of the arguments to the method.
* Since the function must take at least two arguments—self and _cmd, the second and third characters
* must be “@:” (the first character is the return type).
*
* @return The previous implementation of the method identified by \e name for the class identified by \e cls.
*
* @note This function behaves in two different ways:
* - If the method identified by \e name does not yet exist, it is added as if \c class_addMethod were called.
* The type encoding specified by \e types is used as given.
* - If the method identified by \e name does exist, its \c IMP is replaced as if \c method_setImplementation were called.
* The type encoding specified by \e types is ignored.
*/
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
查看 `` 源碼
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
mutex_locker_t lock(runtimeLock);
// 調(diào)用 addMethod 方法,但是此時(shí) addMethod 方法中的 replace 參數(shù)傳入的是 YES
return addMethod(cls, name, imp, types ?: "", YES);
}
通過(guò)上面的 addMethod
源碼分析
- 當(dāng)查找到方法已存在钠糊,直接通過(guò)
_method_setImplementation
方法將傳入的方法實(shí)現(xiàn)挟秤,設(shè)置為查找目標(biāo)方法的實(shí)現(xiàn) - 當(dāng)查找到方法不存在,動(dòng)態(tài)添加到當(dāng)前類中
下面查看一下 _method_setImplementation
方法的實(shí)現(xiàn)原理
static IMP _method_setImplementation(Class cls, method_t *m, IMP imp)
{
runtimeLock.assertLocked();
if (!m) return nil;
if (!imp) return nil;
// 將舊的實(shí)現(xiàn)取出
IMP old = m->imp;
// 直接將新的實(shí)現(xiàn)方法設(shè)置到 method_t 的imp
m->imp = imp;
// Cache updates are slow if cls is nil (i.e. unknown)
// RR/AWZ updates are slow if cls is nil (i.e. unknown)
// fixme build list of classes whose Methods are known externally?
flushCaches(cls);
adjustCustomFlagsForMethodChange(cls, m);
// 返回舊的實(shí)現(xiàn)
return old;
}
查看 method_exchangeImplementations
的方法實(shí)現(xiàn)原理
void method_exchangeImplementations(Method m1, Method m2) {
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
// 直接將傳入的兩個(gè) Method 方法實(shí)現(xiàn)進(jìn)行互換
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);
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
查看 class_getInstanceMethod
方法底層實(shí)現(xiàn)原理
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
static method_t *getMethod_nolock(Class cls, SEL sel)
{
method_t *m = nil;
runtimeLock.assertLocked();
// fixme nil cls?
// fixme nil sel?
ASSERT(cls->isRealized());
// 遍歷當(dāng)前類是否有該方法眠蚂,如果沒(méi)有就遍歷父類
while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
cls = cls->superclass;
}
return m;
}
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// 在這里傳入的對(duì)象是元類對(duì)象
return class_getInstanceMethod(cls->getMeta(), sel);
}