原理:
主要在运行时,将两个方法的实现(impl)进行交换,从而达到调用a(b),实现b(a);只有在method_swizzling语句执行完之后,方法交换才起作用。
交换时机:
应该只在+ (void)load方法中实现method_swizzling;
(在OC的运行时中,+load是在一个类被初始装载时调用,+initialize是在应用第一次调用该类的类方法或实例方法前调用。这两个方法都只有在该类被实现时,才会调用)
(+ load调用时机:
1:+load方法在iOS启动时,加载app各类,分类时调用(在main函数之前,就会调用类,分类的+load方法);
2:父类的+load方法先于子类的+load调用(不包括父类的分类先于子类的分类调用)
3:类的+load方法先于子类的+load方法调用
4:各个类的+load方法调用顺序不确定
)
两种实现:
1):直接实现:(若子类未实现替换的方法,则替换父类对应的方法)
BOOL method_swizzling(Class class, SEL originalSel, SEL swizzleSel) {
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method swizzleMethod = class_getInstanceMethod(class, swizzleSel);
method_exchangeImplematations(originalMethod, swizzleMethod);
return YES;
}
例如:dog继承自animal类,animal实现eat方法,dog实现dog_eat方法;在dog类+load调用时,替换dog的dog_eat与eat方法,实际上是替换animal的eat方法与dog的dog_eat方法。dog调用dog_eat,实际上是调用animal的eat方法;dog调用eat方法,实际上是先调用dog_eat,再调用eat方法(animal实现);aniaml调用eat,实际上是调用dog的dog_eat方法;又因为animal未实现dog_eat方法,所以crash。
2):先添加,后实现:(只在子类中替换方法)
BOOL method_swizzling(Class class, SEL originalSel, SEL swizzleSel) {
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method swizzleMethod = class_getInstanceMethod(class, swizzleSel);
BOOL addSwizzleMethod = class_addMethod(class, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
//考虑swizzleMethod是否实现
if (addSwizzleMethod) {//方法未实现,则先添加方法,再替换方法
class_replaceMethod(class, swizzleMethod, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzleMethod);
}
return YES;
}
具体用途:
页面统计(AOP)- Aspect应用;
数组越界,字典添加nil等crash,均可以通过method_swizzling解决;
实例:
数组越界crash定位:
创建NSArray+UnCrash.m文件:
首先引入#import <objc/runtime.h>
其次:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//以objectAtIndex:取值
Method originalMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method swizzlingMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(qcjr_objectAtIndex:));
BOOL didAddMethod = class_addMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:), method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
if (didAddMethod) {
class_replaceMethod(objc_getClass("__NSArrayI"), @selector(qcjr_objectAtIndex:), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
//以下标形式取值
Method originalSubscriptMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));
Method swizzlingSubscriptMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(qcjr_objectAtIndexedSubscript:));
if (class_addMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:), method_getImplementation(swizzlingSubscriptMethod), method_getTypeEncoding(swizzlingSubscriptMethod))) {
class_replaceMethod(objc_getClass("__NSArrayI"), @selector(qcjr_objectAtIndexedSubscript:), method_getImplementation(originalSubscriptMethod), method_getTypeEncoding(originalSubscriptMethod));
} else {
method_exchangeImplementations(originalSubscriptMethod, swizzlingSubscriptMethod);
}
});
}
- (void)qcjr_objectAtIndexedSubscript:(NSUInteger)index {
NSUInteger count = self.count;
if (index >= count) {
@try {
return [self qcjr_objectAtIndexedSubscript:index];
}
@catch (NSException *exception) {
NSLog(@"-------- %s Crash Because Method %s -------\n", class_getName(self.class), __func__);
NSLog(@"%@",[exception callStackSymbols]);
}
} else {
return [self qcjr_objectAtIndexedSubscript:index];
}
}
- (void)qcjr_objectAtIndex:(NSUInteger)index {
NSUInteger count = self.count;
if (index >= count) {
@try {
return [self qcjr_objectAtIndex:index];
}
@catch (NSException *exception) {
NSLog(@"-------- %s Crash Because Method %s -------\n", class_getName(self.class), __func__);
NSLog(@"%@",[exception callStackSymbols]);
}
} else {
return [self qcjr_objectAtIndex:index];
}
}
这样,在app启动,main函数调用之前,数组的objectAtIndex:均被替换成qcjr_objectAtIndex:方法。
如果,应用中出现数组越界,则会执行try-catch语句,开发者可以执行相应补救措施。
注意:
NSArray类簇:__NSArrayI
NSMutableArray类簇:__NSArrayM
NSDictionary类簇:__NSDictionaryI
NSMutableDictionary类簇:__NSDictionaryM