閑來(lái)無(wú)事弹灭,整理了一下runtime的知識(shí),發(fā)現(xiàn)方法交換里面有個(gè)不明白的點(diǎn):class_addMethod
這個(gè)方法的返回值到底怎么解釋列吼?因?yàn)闇y(cè)試了類方法和實(shí)例方法之后沪编,發(fā)現(xiàn)返回的結(jié)果不一樣,就很迷惑坎匿,在網(wǎng)上搜出來(lái)的結(jié)果越看越糊涂盾剩。。替蔬。后來(lái)第二天一早恍然大悟告私,原來(lái)是原理沒(méi)搞清楚。
1承桥、問(wèn)題展示
先來(lái)碼一下我走過(guò)的坑:
(1)替換 UIImage 類的 init
方法
#import "UIImage+Swizzle.h"
#import <objc/message.h>
@implementation UIImage (Swizzle)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceSel:@selector(init) withNewSel:@selector(qq_init)];
});
}
+ (void)swizzleInstanceSel:(SEL)oldSel withNewSel:(SEL)newSel {
Class class = self.class;
Method oldM = class_getInstanceMethod(class, oldSel);
Method newM = class_getInstanceMethod(class, newSel);
BOOL didAdd = class_addMethod(class, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
if (didAdd) {
NSLog(@"swizzleInstanceSel * didAdd");
class_replaceMethod(class, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
}
else {
NSLog(@"swizzleInstanceSel * didn'tAdd ----> exchange!");
method_exchangeImplementations(oldM, newM);
}
}
- (instancetype)qq_init {
NSLog(@"自定義的qq_init方法 - %s", __func__);
return [self qq_init];
}
@end
然后在 ViewController 類中調(diào)用 UIImage 的 init
方法:UIImage * image = [[UIImage alloc] init];
驻粟,結(jié)果控制器的打印如下:
2018-06-15 11:00:37.155152+0800 AddMethodTest[22950:4869406] swizzleInstanceSel * didAdd
2018-06-15 11:00:37.274999+0800 AddMethodTest[22950:4869406] 自定義的qq_init方法 - -[UIImage(Swizzle) qq_init]
從打印中可以看出,class_addMethod
方法的返回值 didAdd 為YES快毛,即調(diào)用了 class_replaceMethod
方法格嗅。調(diào)用 UIImage 的 init
方法,實(shí)際上運(yùn)行的時(shí)候走的是我們自定義的 qq_init
方法唠帝,達(dá)到效果屯掖,然而看下面??
(2)替換 UIImage 類的 imageNamed
方法
#import "UIImage+Swizzle.h"
#import <objc/message.h>
@implementation UIImage (Swizzle)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleClassSel:@selector(imageNamed:) withNewSel:@selector(qq_imageNamed:)];
});
}
+ (void)swizzleClassSel:(SEL)oldSel withNewSel:(SEL)newSel {
Class class = self.class;
Method oldM = class_getClassMethod(class, oldSel);
Method newM = class_getClassMethod(class, newSel);
BOOL didAdd = class_addMethod(class, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
if (didAdd) {
NSLog(@"swizzleClassSel * didAdd");
class_replaceMethod(class, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
}
else {
NSLog(@"swizzleClassSel * didn'tAdd ----> exchange!");
method_exchangeImplementations(oldM, newM);
}
}
+ (UIImage *)qq_imageNamed:(NSString *)name {
NSLog(@"自定義的imageNamed方法 - %s", __func__);
return [self qq_imageNamed:name];
}
@end
然后在 ViewController 類中調(diào)用 UIImage 的 imageNamed
方法:UIImage * image = [UIImage imageNamed:@"1"];
,結(jié)果控制器的打印如下:
2018-06-15 11:12:13.806120+0800 AddMethodTest[22955:4873159] swizzleClassSel * didAdd
從打印中可以看出襟衰,class_addMethod
方法的返回值 didAdd 為YES贴铜,即調(diào)用了 class_replaceMethod
方法。但是調(diào)用 UIImage 的 imageNamed
方法瀑晒,實(shí)際上運(yùn)行的時(shí)候并沒(méi)有走我們自定義的 qq_imageNamed
方法绍坝,這是個(gè)什么情況?苔悦?轩褐?
我就很費(fèi)解啊玖详!為什么相同的邏輯在同一個(gè)類的同一個(gè)方法中調(diào)用的結(jié)果不一樣把介??
2蟋座、原理分析
百思不得解之后拗踢,我翻了一下系統(tǒng)中 class_addMethod
的方法介紹,
/**
* 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);
很明確向臀,如果method添加成功就返回YES巢墅,否則返回NO(比如類中已經(jīng)包含這個(gè)方法實(shí)現(xiàn))。好像忽然就明白了券膀,君纫, 實(shí)例方法的方法列表保存在類中,而類方法的方法列表保存在元類中三娩。這樣好像就說(shuō)得通了庵芭。
其實(shí)這些c方法操作的都是結(jié)構(gòu)體, class_addMethod
的第一個(gè)屬性是Class雀监,新增的也是這個(gè)Class里的方法双吆。我這里的 class_addMethod
是給 self.class(class_addMethod方法的第一個(gè)參數(shù)) 添加方法,即給 UIImage 類添加方法会前,而 UIImage 這個(gè)類中保存的都是實(shí)例方法好乐,故此這地方的 class_addMethod
只能在給這個(gè)類添加實(shí)例方法的時(shí)候使用。換句話說(shuō)瓦宜,就是 Class 保存的是 instance 的方法蔚万,所以加的那些方法最終體現(xiàn)在實(shí)例上。
3临庇、解決方法
搞清楚問(wèn)題后就知道了反璃,如果我想替換掉類方法昵慌,就要把第一個(gè)參數(shù)給換成元類的本體,即 objc_getMetaClass(object_getClassName(self))
就可以了淮蜈。
+ (void)swizzleClassSel:(SEL)oldSel withNewSel:(SEL)newSel {
Class class = objc_getMetaClass(object_getClassName(self));
Method oldM = class_getClassMethod(class, oldSel);
Method newM = class_getClassMethod(class, newSel);
BOOL didAdd = class_addMethod(class, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
if (didAdd) {
NSLog(@"swizzleInstanceSel * didAdd");
class_replaceMethod(class, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
}
else {
NSLog(@"swizzleInstanceSel * didn'tAdd ----> exchange!");
method_exchangeImplementations(oldM, newM);
}
}
打印結(jié)果如下:
2018-06-15 11:41:41.630059+0800 AddMethodTest[22958:4876411] swizzleInstanceSel * didn'tAdd ----> exchange!
2018-06-15 11:41:41.630059+0800 AddMethodTest[22958:4876411] 自定義的imageNamed方法 - +[UIImage(Swizzle) qq_imageNamed:]
這樣就解釋的通了斋攀。
PS:
類和元類的結(jié)構(gòu)體是一致的,所以很多關(guān)系都是可以搬過(guò)來(lái)的梧田。
實(shí)例-類淳蔼,類-元類的區(qū)別在于,實(shí)例是開(kāi)發(fā)new出來(lái)的用于承載業(yè)務(wù)信息的裁眯,類是系統(tǒng)new出來(lái)的鹉梨,用于描述實(shí)例的,表面上的用處是大不一樣的穿稳,但是挖到深層結(jié)構(gòu)就是通用的存皂。
也不知道我理解的對(duì)不對(duì),如果有問(wèn)題司草,歡迎大家指正艰垂,將不勝感激!