1.
父类有方法A,子类没有重写方法A,在子类中进行方法A的交换,极有可能有幺蛾子。例:
.h 中
@interface Person : NSObject
- (void)run;
@end
@interface Man : Person
@end
@interface Man(Swizzling)
@end
.m 中
@implementation Person
- (void)run {
NSLog(@"Person run");
}
@end
@implementation Man
@end
@implementation Man(Swizzling)
+ (void)load {
SEL origSel = @selector(run);
SEL swizzlingSel = @selector(swizzlingRun);
Method fromMethod = class_getInstanceMethod([self class], origSel);
Method toMethod = class_getInstanceMethod([self class], swizzlingSel);
method_exchangeImplementations(fromMethod, toMethod);
}
- (void)swizzlingRun {
NSLog(@"swizzlingRun");
[self swizzlingRun];
}
@end
调用:
- (void)viewDidLoad {
[super viewDidLoad];
Man *m = [[Man alloc] init];
[m run];
Person *p = [[Person alloc] init];
[p run];
}
打印:
2020-06-15 11:28:14.267950+0800 Demo[44980:8856574] swizzlingRun
2020-06-15 11:28:14.268137+0800 Demo[44980:8856574] Person run
2020-06-15 11:28:14.268365+0800 Demo[44980:8856574] swizzlingRun
2020-06-15 11:28:14.268543+0800 Demo[44980:8856574] -[Person swizzlingRun]: unrecognized selector sent to instance 0x6000014881b0
2020-06-15 11:28:14.273330+0800 Demo[44980:8856574] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person swizzlingRun]: unrecognized selector sent to instance 0x6000014881b0'
可以看到,子类Man
调用run
还是没问题的,但是Person
就直接crash了。因为
Method fromMethod = class_getInstanceMethod([self class], origSel);
子类没有实现run
方法,fromMethod
获取到的是父类的run IMP
。也就是父类的run
与swizzlingRun
交换了。
[m run]
会去父类中找run的IMP,由于已经交换,会调用到swizzlingRun
,在swizzlingRun
中调用[self swizzlingRun];
,因为子类确实有swizzlingRun 的 SEL
,又因为已经交互所以会调用到父类的run
。也就会出现打印的情况。
[p run]
由于已经交换,会调用到swizzlingRun
,在swizzlingRun
中调用[self swizzlingRun];
,因为父类没有有swizzlingRun 的 SEL
,所以就直接报unrecognized
crash掉了。
2.
对上述例1优化:
.h 中
@interface Person : NSObject
- (void)run;
@end
@interface Man : Person
@end
@interface Man(Swizzling)
@end
.m 中
@implementation Person
- (void)run {
NSLog(@"Person run");
}
@end
@implementation Man
@end
@implementation Man(Swizzling)
+ (void)load {
SEL origSel = @selector(run);
SEL swizzlingSel = @selector(swizzlingRun);
Method fromMethod = class_getInstanceMethod([self class], origSel);
Method toMethod = class_getInstanceMethod([self class], swizzlingSel);
/*
向本类中添加方法(原始方法的SEL,交换后方法的IMP),如果didAddMethod为YES,
说明本类没有重写继承来的原方法,此时fromMethod为父类中的Method,再将swizzlingSel的IMP指向fromMethod的IMP。
*/
BOOL didAddMethod = class_addMethod([self class], origSel, method_getImplementation(toMethod), method_getTypeEncoding(toMethod));
if (didAddMethod) {
class_replaceMethod([self class], swizzlingSel, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
} else {
method_exchangeImplementations(fromMethod, toMethod);
}
}
- (void)swizzlingRun {
NSLog(@"swizzlingRun");
[self swizzlingRun];
}
@end
调用:
- (void)viewDidLoad {
[super viewDidLoad];
Man *m = [[Man alloc] init];
[m run];
Person *p = [[Person alloc] init];
[p run];
}
输出:
2020-06-15 14:27:24.386779+0800 Demo[49010:8964879] swizzlingRun
2020-06-15 14:27:24.387890+0800 Demo[49010:8964879] Person run
2020-06-15 14:27:24.387973+0800 Demo[49010:8964879] Person run
Man
中本没有run
的实现,但是向Man
中添加了(run SEL:swizzlingRun IMP)
,所以[m run]
会调用到swizzlingRun
,输出NSLog(@"swizzlingRun");
又因为把swizzlingRun
的IMP替换到了父类的run
,所以[self swizzlingRun];
会调到父类run
。
如果Man中重写了run
@implementation Man
- (void)run {
[super run];
NSLog(@"Man run");
}
@end
同样的调用方式输出:
2020-06-15 14:58:41.952407+0800 Demo[49749:8985021] swizzlingRun
2020-06-15 14:58:41.952529+0800 Demo[49749:8985021] Person run
2020-06-15 14:58:41.952599+0800 Demo[49749:8985021] Man run
2020-06-15 14:58:41.952679+0800 Demo[49749:8985021] Person run
这样可以解决crash,且同时完成hook。
当然这只能hookMan以及Man子类中的run
,不能hook到父类Person
的方法。
如果Man
的子类重写了run
却没有调用父类方法,那就hook不到子类的run
了。
有时只想hookMan
中的run
,不想hook其子类的run
(调用父类run)是做不到的,子类一旦调用了父类方法就一定会被影响(这一点Aspects很好,可以只hook单个实例的方法)。
所以一般hook的方法存在继承关系比如viewDidLoad
就直接在UIViewController
的分类中进行hook。
网友评论