前话
这几天在系统的学习 runtime,在学习 runtime 的基础使用案例中,"方法替换"这种使用情况下,发现有两种写法. 其实也不是两种写法,准确的来说一种是比较严谨的,另一种则没有那么严谨.
发现这两种写法的差异后,我主要集中在下列:
class_addMethod
class_replaceMethod
method_exchangeImplementations
哪个方法的具体作用.
下面,这篇文章就这两种写法
和上述三种方法
的区别.
第一种写法
在《OC最实用的runtime总结,面试、工作你看我就足够了!》的时候,它里边的写法是简单的获取到被替换和替换方法的Method.然后直接使用method_exchangeImplementations
进行方法的替换. 最开始使用的时候,因为测试范例比较简单,所以并没有发现这样写的弊端.但是确实能够实现方法替换的效果. 代码如下:
+(void)load{
//获取两个类的方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(ll_imageName:));
//开始交换方法实现
method_exchangeImplementations(m1, m2);
}
在后来看到《runtime详解》的时候,发现作者的写法并不是这样,虽然作者添加少量注释,但是愚钝的我还没有想清楚,这也是这篇文章的初衷,也是下一小结的由来.
第二种写法
上一节的这种情况虽然能够实现我们想要的效果.但是我们有没有想过这种情况:
" 周全起见,有两种情况要考虑一下。第一种情况是要复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。第二种情况是这个方法已经存在于目标类中(does existin the class itself)。这两种情况要区别对待。 (译注: 这个地方有点要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的。) 对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。 对于第二情况(在目标类重写的方法)。这时可以通过method_exchangeImplementations
来完成交换."
---- 以上来自:《Objective-C的方法替换》
+(void)load{
NSString *className = NSStringFromClass(self.class);
NSLog(@"classname %@", className);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//要特别注意你替换的方法到底是哪个性质的方法
// When swizzling a Instance method, use the following:
// Class class = [self class];
// When swizzling a class method, use the following:
Class class = object_getClass((id)self);
SEL originalSelector = @selector(systemMethod_PrintLog);
SEL swizzledSelector = @selector(ll_imageName);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(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);
}
});
}
解析:
上面提到的:
dispatch_once
这里不是“单例”,是保证方法替换只执行一次.
说明:
systemMethod_PrintLog:
被替换方法ll_imageName:
替换方法
class_addMethod:
如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
1.如果返回成功:则说明被替换方法没有存在.也就是被替换的方法没有被实现,我们需要先把这个方法实现,然后再执行我们想要的效果,用我们自定义的方法去替换被替换的方法. 这里使用到的是class_replaceMethod
这个方法. class_replaceMethod
本身会尝试调用class_addMethod
和method_setImplementation
,所以直接调用class_replaceMethod
就可以了)
2.如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换即
另外:
- 我们可以利用
method_exchangeImplementations
来交换2个方法中的IMP - 我们可以利用
class_replaceMethod
来修改类 - 我们可以利用
method_setImplementation
来直接设置某个方法的IMP
其实我们如果 研究过 AFN 代码的话,会发现, AFN 就是第二种写法.在AFURLSessionManager.m
的第296行:
static inline void af_swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
详尽的代码请查看 Demo.
下载地址
具体的 Demo 代码可以在我的 GitHub 上找到 Demo地址
其它
关于 load 的调用次数问题,大家可以查看这两篇文章.+(void)load和+(void)initialize可当做普通类方法(Class Method)调用的.《NSObject的load和initialize方法!》和《Objective C类方法load和initialize的区别》
参考文章
交流
希望能和大家交流技术
我的博客地址: http://www.lilongcnc.cc/
网友评论
void dynamicMethodIMP(id sel,SEL _cmdd){
NSLog(@"resolveInstanceMethod调用成功");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @Selector(method)) {
// class_addMethod([self class],sel,(IMP));
class_addMethod([self class],sel,(IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
当class_addMethod为该类添加一个方法的实现,该实现是swizz方法的实现
然后调用class_replaceMethod替换swizz方法的实现为original方法的实现,
问题:如果子类没有重写,那么originalMethod拿到的是父类的方法实现,调用class_replaceMethod不是把swizz方法的实现替换了父类的实现,那跟调用直接method_exchangeImplementations有什么区别吗? 感谢,望指教
https://gist.github.com/atom2ueki/79dbf4270b39beda67ae3774a6e99661
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
应该是拿original对应父类方法的实现替换swizzle方法的实现,也就是说class_replaceMethod方法仅仅是替换掉了swizzle方法的实现,但没替换original对应父类方法的实现;而method_exchangeImplementations是交换了swizzle方法和original方法的实现。
另外补充下,就像上面分析的,class_replaceMethod本身会尝试调用class_addMethod和method_setImplementation,如果swizzledSelector没有方法实现则调用class_addMethod,有实现则调用method_setImplementation覆盖掉原来的实现。
Class class = object_getClass((id)self);
SEL originalSelector = @selector(systemMethod_PrintLog);
SEL swizzledSelector = @selector(ll_imageName);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
博主你好,看你的示例代码里边,交换的是类方法(Class class = object_getClass((id)self);),是不是应该使用class_getClassMethod这个函数?
- (void)viewDidLoad {
[super viewDidLoad];
[ViewController go];
[ViewController stop];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL s1 = @selector(go);
SEL s2 = @selector(stop);
Class class = [self class];
Method m1 = class_getClassMethod(class, s1);
Method m2 = class_getClassMethod(class, s2);
// Method m1 = class_getInstanceMethod(self.class, s1);
// Method m2 = class_getInstanceMethod(self.class, s2);
BOOL success = class_addMethod(class, s1, method_getImplementation(m2), method_getTypeEncoding(m2));
if (success){
class_replaceMethod(class, s2, method_getImplementation(m1), method_getTypeEncoding(m1));
}else{
method_exchangeImplementations(m1, m2);
}
});
NSLog(@"--------------------------------------------");
[ViewController go];
[ViewController stop];
}
+ (void)go {
NSLog(@"Go!");
}
+ (void)stop {
NSLog(@"Stop!");
}
打印出来:
2017-01-22 17:43:00.012 RuntimeDemo[3216:1376638] Go!
2017-01-22 17:43:00.012 RuntimeDemo[3216:1376638] Stop!
2017-01-22 17:43:00.012 RuntimeDemo[3216:1376638] --------------------------------------------
2017-01-22 17:43:00.012 RuntimeDemo[3216:1376638] Go!
2017-01-22 17:43:00.012 RuntimeDemo[3216:1376638] Stop!
给类添加类方法,要在类的元类上添加。下面这样写就可以。因为类也是对象,它的类方法是在它的元类上的。
Class metaClass = object_getClass(class);// <------修改
BOOL didAddMethod = class_addMethod(metaClass,origSel,
method_getImplementation(altMethod),
method_getTypeEncoding(altMethod));
if (didAddMethod) {
class_replaceMethod(metaClass,altSel,
method_getImplementation(origMethod),
method_getTypeEncoding(origMethod));
我简单看了下,你的代码加上:- (void)go {
NSLog(@" - Go!");
}
- (void)stop {
NSLog(@"- Stop!");
}
类方法就可以替换了. 具提原因我显现在也不是很清楚.之前测试还真没有遇到这个问题.
初步猜测,可能可能是判断的方法有问题.
打印:
2017-01-22 17:44:20.051 RuntimeDemo[3238:1384694] Go!
2017-01-22 17:44:20.051 RuntimeDemo[3238:1384694] Stop!
2017-01-22 17:44:20.052 RuntimeDemo[3238:1384694] --------------------------------------------
2017-01-22 17:44:20.052 RuntimeDemo[3238:1384694] Stop!
2017-01-22 17:44:20.052 RuntimeDemo[3238:1384694] Go!
1. 子类方法里没有这个方法,
2. 分类里写了这个方法的实现,
3. add, 增加方法成功, 上面的写法, 已经重写, originalSelector的名字, swizzledMethod的实现和类型, Demo试了一下