今天想用method swizzling的时候,突然想到了一个问题,如果swizzle了一个父类(而不是当前类)的方法,会怎么样呢?
于是写了点代码试验一下。
首先定义一个基类XSQBaseObject
,其中有一个baseMethod
方法:
//XSQBaseObject.h
#import
@interface XSQBaseObject : NSObject
- (void)baseMethod;
@end
//XSQBaseObject.m
#import "XSQBaseObject.h"
@implementation XSQBaseObject
- (void)baseMethod {
NSLog(@"this is base method");
}
@end
然后定义一个子类XSQChildObject
继承自XSQBaseClass
,子类里没有写任何方法。
//XSQChildObject.h
#import "XSQBaseObject.h"
@interface XSQChildObject : XSQBaseObject
@end
//XSQChildObject.m
#import "XSQChildObject.h"
@implementation XSQChildObject
@end
然后给XSQChildObject
增加一个category,用来做swizzling:
//XSQChildObject+Swizzling.h
#import "XSQChildObject.h"
@interface XSQChildObject (Swizzling)
@end
//XSQChildObject+Swizzling.m
#import "XSQChildObject+Swizzling.h"
#import
@implementation XSQChildObject (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(baseMethod);
SEL swizzledSelector = @selector(xxx_baseMethod);
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);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_baseMethod {
NSLog(@"swizzle~~");
[self xxx_baseMethod];
}
@end
swizzling的代码参考自Objective-C Runtime 运行时之四:Method Swizzling
然后给XSQChildObject
的实例发送消息:
XSQBaseObject *baseObject = [[XSQBaseObject alloc] init];
[baseObject baseMethod];
XSQChildObject *childObject = [[XSQChildObject alloc] init];
[childObject baseMethod];
竟然平安无事的得到了结果:
2016-05-09 20:46:06.220 XSQSwizzlingDemo[4235:1321820] this is base method
2016-05-09 20:46:08.055 XSQSwizzlingDemo[4235:1321820] swizzle~~
2016-05-09 20:46:08.056 XSQSwizzlingDemo[4235:1321820] this is base method
明明我还在担心,父类的方法如果被swizzle了,应该会出现这样的情景:
给XSQBaseObject
的实例发送baseMethod
消息时,实际调用的是xxx_baseMethodIMP
的实现,而xxx_baseMethodIMP
中还给[self xxx_baseMethod]
发送了消息,而XSQBaseObject
中没有xxx_baseMethod
这个selector,走到这里应该崩溃才对。
仔细看一下swizzling的代码,发现这段代码中已经对我担心的问题做了处理:
/*
* class_addMethod方法会给这个类添加一个方法
* 如果这个类(本身,不包括父类)已经有了originalSelector,则无法添加成功,同时返回NO
* 所以这里的意义是:如果这个类(自身,不包括父类)没有originSelector,则给它添加一个方法,而方法实现对应于swizzledMethod
*/
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
/*
* 如果给这个类添加originSelector成功,则让这个类的swizzledSelector的实现变成originalMethod
*/
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
这段代码应对的就是,如果originalSelector是父类中的方法,而子类也没有重写它,这时就不能直接交换两个方法的实现,而是要给子类也添加一个originalSelector的实现。
如果把上面这段代码删除,全部用method_exchangeImplementations
来做swizzle:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(baseMethod);
SEL swizzledSelector = @selector(xxx_baseMethod);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
果然运行时崩溃了:
2016-05-09 21:10:55.523 XSQSwizzlingDemo[4247:1325552] swizzle~~
2016-05-09 21:10:55.526 XSQSwizzlingDemo[4247:1325552] -[XSQBaseObject xxx_baseMethod]: unrecognized selector sent to instance 0x155e189d0
2016-05-09 21:10:55.527 XSQSwizzlingDemo[4247:1325552] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[XSQBaseObject xxx_baseMethod]: unrecognized selector sent to instance 0x155e189d0'
和图片中画的一致,给XSQBaseObject
的实例发送baseMethod
消息时,执行了xxx_baseMethod
的实现(所以第一行输出了“swizzle~~”),然后发送了[self xxx_baseMethod]
消息给XSQBaseObject
的实例,自然抛出了unrecognized selector异常。
参考
Objective-C Runtime 运行时之四:Method Swizzling