52个有效方法(13) - 用“方法调配技术”调试“黑盒方法”
2018-09-03 本文已影响8人
SkyMing一C
方法调配技术也就是方法交换技术。
//获取类的实例方法 返回一个Method对象
class_getInstanceMethod(Class obj, SEL cmd)
//获取类的类方法 返回一个Method对象
class_getClassMethod(Class obj, SEL cmd)
//添加一个新的方法和该方法的具体实现
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//Class cls 你要添加新方法的那个类
//SEL name 要添加的方法
//IMP imp 指向实现方法的指针 就是要添加的方法的实现部分
//const char *types 我们要添加的方法的返回值和参数 method_getTypeEncoding(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
//替换方法的实现
method_exchangeImplementations(method1, method2)
交换对象方法
//.h
#import <UIKit/UIKit.h>
@interface UIActionSheet(Swizzling)
@end
#import "UIActionSheet+Swizzling.h"
@implementation UIActionSheet(Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获取class
Class class = [self class];
// 获取原有SEL
SEL originSelector = @selector(dismissWithClickedButtonIndex:animated:);
// 获取交换SEL
SEL newSelector = @selector(swizzleDismissWithClickedButtonIndex:animated:);
// 获取原有方法
Method oriMethod = class_getInstanceMethod(class, originSelector);
// 获取交换方法
Method newMethod = class_getInstanceMethod(class, newSelector);
// 注意:不能直接交换方法实现,需要判断原有方法是否存在,存在才能交换
// 如何判断?添加原有方法,如果成功,表示原有方法不存在,失败,表示原有方法存在
// 原有方法可能没有实现,所以这里添加方法实现,用自己方法实现
// 这样做的好处:方法不存在,直接把自己方法的实现作为原有方法的实现,调用原有方法,就会来到当前方法的实现
BOOL isAddedMethod = class_addMethod(class, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
if (isAddedMethod) {
// 如果 class_addMethod 成功了,说明之前 class 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续这个方法被调用时可能会 crash。
class_replaceMethod(class, newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
method_exchangeImplementations(oriMethod, newMethod);
}
});
}
-(void)swizzleDismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated{
//这个方法是准备和`dismissWithClickedButtonIndex:animated:`这儿方法互换的。在运行期,`swizzleDismissWithClickedButtonIndex:animated: `选择子实际上对应于原有的`dismissWithClickedButtonIndex:animated:`方法的实现
[self swizzleDismissWithClickedButtonIndex:buttonIndex animated:animated];
//修复keyWindow 不正确的问题
[[UIApplication sharedApplication].delegate.window makeKeyWindow];
}
@end
- 一般情况下,类别里的方法会重写掉主类里相同命名的方法。如果有两个类别实现了相同命名的方法,只有一个方法会被调用。但
+load:
是个特例,当一个类被读到内存的时候,runtime
会给这个类及它的每一个类别都发送一个+load:
消息。使用GCD的dispatch_once
可以保证交换两个实例方法的实现只进行一次。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//do something...
});
}
- 不能直接交换方法实现,需要判断原有方法是否存在,存在才能交换。
- 如何判断?添加原有方法,如果成功,表示原有方法不存在,失败,表示原有方法存在。
- 原有方法可能没有实现,所以这里添加方法实现,用自己方法实现。
- 这样做的好处:方法不存在,直接把自己方法的实现作为原有方法的实现,调用原有方法,就会来到当前方法的实现。
//获取class
Class class = [self class];
// 获取原有SEL
SEL originSelector = @selector(dismissWithClickedButtonIndex:animated:);
// 获取交换SEL
SEL newSelector = @selector(swizzleDismissWithClickedButtonIndex:animated:);
// 获取原有方法
Method oriMethod = class_getInstanceMethod(class, originSelector);
// 获取交换方法
Method newMethod = class_getInstanceMethod(class, newSelector);
BOOL isAddedMethod = class_addMethod(class, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
if (isAddedMethod) {
// 如果 class_addMethod 成功了,说明之前 class 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续这个方法被调用时可能会 crash。
class_replaceMethod(class, newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
method_exchangeImplementations(oriMethod, newMethod);
}
-
swizzleDismissWithClickedButtonIndex:animated:
是准备和dismissWithClickedButtonIndex:animated:
这儿方法互换的。 - 在运行期,
swizzleDismissWithClickedButtonIndex:animated:
选择子实际上对应于原有的dismissWithClickedButtonIndex:animated:
方法的实现。
-(void)swizzleDismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated{
//这个方法是准备和`dismissWithClickedButtonIndex:animated:`这儿方法互换的。在运行期,`swizzleDismissWithClickedButtonIndex:animated: `选择子实际上对应于原有的`dismissWithClickedButtonIndex:animated:`方法的实现
[self swizzleDismissWithClickedButtonIndex:buttonIndex animated:animated];
//修复keyWindow 不正确的问题
[[UIApplication sharedApplication].delegate.window makeKeyWindow];
}
交换类方法
//.h
#import <Foundation/Foundation.h>
@interface UIFont (DefaultSize)
@end
//.m
#import "UIFont+DefaultSize.h"
#import <objc/runtime.h>
@implementation UIFont (DefaultSize)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获取class
Class class = object_getClass((id)self);
// 获取原有SEL
SEL originSelector = @selector(preferredFontForTextStyle:);
// 获取交换SEL
SEL newSelector = @selector(swizzlePreferredFontForTextStyle:);
// 获取原有方法
Method oriMethod = class_getClassMethod(class, originSelector);
// 获取交换方法
Method newMethod = class_getClassMethod(class, newSelector);
// 注意:不能直接交换方法实现,需要判断原有方法是否存在,存在才能交换
// 如何判断?添加原有方法,如果成功,表示原有方法不存在,失败,表示原有方法存在
// 原有方法可能没有实现,所以这里添加方法实现,用自己方法实现
// 这样做的好处:方法不存在,直接把自己方法的实现作为原有方法的实现,调用原有方法,就会来到当前方法的实现
BOOL isAddedMethod = class_addMethod(class, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
if (isAddedMethod) {
// 如果 class_addMethod 成功了,说明之前 class 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续这个方法被调用时可能会 crash。
class_replaceMethod(class, newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
method_exchangeImplementations(oriMethod, newMethod);
}
});
}
+ (UIFont *)swizzlePreferredFontForTextStyle:(UIFontTextStyle)style{
return [UIFont systemFontOfSize:17];
}
@end
-
类方法获取class实例对象方法为
Class class = object_getClass((id)self);
,通过object_getClass
获取到的是类的元类。对象方法获取class实例对象方法为Class class = [self class];
。
创建对象的类本身也是对象,称为类对象,类对象中存放的是描述实例相关的信息,例如实例的成员变量,实例方法。类对象里的isa指针指向Subclass(meta),Subclass(meta)也是一个对象,是原类对象,原类对象中存放的是描述类相关的信息,例如类方法。
-
类方法获取方法为
class_getClassMethod
对象方法获取方法为class_getInstanceMethod
。
要点
-
在运行期,可以向类中新增或替换选择子所对应的方法实现。
-
使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
-
一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。