1. 什么是Method swizzling与ISA swizzling
-
Method swizzling
:指在运行时替换两个方法的实现(IMP),例如swizzling(Method A, Method B),交换后则变为Method A —> B的IMP,而Method B —> A的IMP;常用于APP统计事件无痕埋点,修改系统API实现等。 -
ISA swizzling
:对象的isa指针定义了它的类,所以ISA swizzling指修改对象所指向的类。KVO则是使用该技术实现的,还有Zombie objects检测也用到了该技术
2. 如何实现Method swizzling
要实现方法混写,需要用到运行时API:void method_exchangeImplementations(Method m1, Method m2)
,具体实现如下。
//定义设置Error宏
#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \
if (ERROR_VAR) { \
NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
*ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
code:-1 \
userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
}
#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)
#pragma mark - Swizzle
@implementation NSObject (NE_Swizzle)
//方法混写
+ (BOOL)ne_swizzleMethod:(SEL)origSel withMethod:(SEL)altSel error:(NSError**)error
{
Method origMethod = class_getInstanceMethod(self, origSel);
if (!origMethod) {
SetNSError(error, @"original method %@ not found for class %@", NSStringFromSelector(origSel), [self class]);
return NO;
}
Method altMethod = class_getInstanceMethod(self, altSel);
if (!altMethod) {
SetNSError(error, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel), [self class]);
return NO;
}
class_addMethod(self,
origSel,
class_getMethodImplementation(self, origSel),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel,
class_getMethodImplementation(self, altSel),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origSel), class_getInstanceMethod(self, altSel));
return YES;
}
//Class实质上也是一个对象
+ (BOOL)ne_swizzleClassMethod:(SEL)origSel withClassMethod:(SEL)altSel error:(NSError**)error
{
return [object_getClass((id)self) ne_swizzleMethod:origSel withMethod:altSel error:error];
}
3. 如何实现ISA swizzling
- 要实现方法混写,需要用到运行时API:
Class object_setClass(id obj, Class cls)
具体实现如下。
#pragma mark - Swizzle
@implementation NSObject (NE_Swizzle)
- (BOOL)ne_setClass:(Class)altClass error:(NSError**)error
{
if (class_getInstanceSize([self class]) == class_getInstanceSize(altClass)) {
object_setClass(self, altClass);
return YES;
} else {
SetNSError(error, @"classes must be same size to swizzle. original: %@ alternate: %@" , NSStringFromClass(altClass), NSStringFromClass([self class]));
return NO;
}
}
@end
-
重要:被混写的两个类的大小一定要相等,也就是说对象即将指向的新Class中声明任何的ivar或者合成属性。因为被混写对象的内存是已经分配好的,如果添加ivar,那它们就会指向已分配内存外的区域,那样很容易覆盖内存中这个对象后面对象的isa指针,导致
EXC_BAD_ACCESS
或者SIGABRT
。例子如下:
@interface People : NSObject
@property (nonatomic, assign) NSUInteger age;
@end
@implementation People
@end
@interface Student : People
@property (nonatomic, strong) NSString *name;
@end
@implementation Student
@end
// main
int main(int argc, char * argv[]) {
@autoreleasepool {
People *p1 = [People new];
object_setClass(p1, [Student class]);
p1.age = 16;
People *p2 = [People new];
p2.age = 18;
return YES;
}
}
- 如果Student存在name属性,由于混写的类的大小不一致,所以代码运行则发生崩溃。因此必须去除name属性。
4. 二者有什么区别
-
Method swizzling
- 影响一个类的所有对象(实例)
- 对象都指向的同一个类
- 调用API:
void method_exchangeImplementations(Method m1, Method m2)
实现混写
-
ISA swizzling
- 只会影响到当前目标对象
- 对象的类会发生变化
- 使用子类技术混写,即必须保证混写类的大小相等,调用API:
Class object_setClass(id obj, Class cls)
实现
网友评论