方法查找流程
我们知道当查找一个方法时,会通过isa指针,从其类中查找,类中没有,则从类的父类查找,当root的类没有这个方法时,就进入了我们的动态解析机制,消息转发。
通过观察lookUpImpOrForward
的实现,我们同样可以证实,方法的查询逻辑为对象中没有,就从父类中查找,执行顺序为:
1. 无锁的缓存查找
2. 如果类没有实现(isRealized)或者初始化(isInitialized),实现或者初始化类
3. 加锁
4. 缓存以及当前类中方法的查找
5. 尝试查找父类的缓存以及方法列表
6. 没有找到实现,尝试方法解析器 resolveInstanceMethod:
7. 进行消息转发 forwardingTargetForSeletor:
8. 解锁、返回实现
消息转发机制
消息转发机制在我的 OC Runtime探索 一文中已经说明,这里讲解下其关键点,以及原理。
消息无法处理
最终会响应到doesNotRecognizeSelector:
方法中
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
在这里之前我们可以有3
处地方可以防止doesNotRecognizeSelector:
的触发,下面依次介绍。
_class_resolveMethod
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { //当不是元类
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else { //当是元类时
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
当是元类时,它会走_class_resolveClassMethod
方法之外,还会在没有找到的时候,走_class_resolveInstanceMethod
方法
假设,当我们没有找到一个类方法,并且我们没有写+resolveClassMethod
的时候,怎么办呢?_class_resolveMethod
的这段代码告诉我们,还要进行一步补救措施_class_resolveInstanceMethod
,即从其元类中查找实例方法。
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { //当不是元类
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else { //当是元类时
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
意思是不是,无论怎样,都会走_class_resolveInstanceMethod
对吧,只要你没有写+resolveClassMethod
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
......
}
因为_class_resolveInstanceMethod
必定会走(不进行额外操作的情况),我们只需要重写我们如何重写resolveInstanceMethod
方法,这里使用分类才能重写NSObject。
#import "NSObject+HCResolver.h"
#import <objc/runtime.h>
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
@implementation NSObject (LGKC)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"NSObject+HCResolver == %s",__func__);
if (sel == @selector(walk)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return NO;
}
@end
其实我们就只在NSObject实现的基础上加上了自己的逻辑,在NSObject.mm
的源码中
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
这样就能针对方法进行处理,这也是处理很多第三方库的报错的常见方法。
和respondsToSelector:关联
根据GNU
源码,respondsToSelector:
逻辑通过了resolveClassMethod:和resolveInstanceMethod
,所以如果处理了resolveClassMethod:和resolveInstanceMethod
并返回YES
,respondsToSelector:
就会返回YES
- (BOOL) respondsToSelector: (SEL)aSelector
{
Class cls = object_getClass(self);
if (aSelector == 0)
{
if (GSPrivateDefaultsFlag(GSMacOSXCompatible))
{
[NSException raise: NSInvalidArgumentException
format: @"%@ null selector given",
NSStringFromSelector(_cmd)];
}
return NO;
}
if (class_respondsToSelector(cls, aSelector))
{
return YES;
}
if (class_isMetaClass(cls))
{
return [(Class)self resolveClassMethod: aSelector];
}
else
{
return [cls resolveInstanceMethod: aSelector];
}
}
监听错误避免崩溃
有时候,我们想在出错的时候,监听这个错误,并做自己的处理,如何在不破坏其原有逻辑情况下,做到错误抛出和处理。
我们只能在消息转发的最后forwardInvocation:
进行处理,因为如果在前面处理,无法保证在forwardingTargetForSelector:
和forwardInvocation:
是否进行了适配方法的操作。
新建一个NSObject
分类NSObject+LGKC
#import "NSObject+LGKC.h"
#import <objc/runtime.h>
void dynamicMethodIMP(id self, SEL sel) {
NSLog(@" >> dynamicMethodIMP unrecognized selector [%s] sent to %@",sel_getName(sel),self);
}
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector)
{
if (!cls) {
return;
}
/* if current class not exist selector, then get super*/
Method originalMethod = class_getInstanceMethod(cls, origSelector);
Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
/* add selector if not exist, implement append with method */
if (class_addMethod(cls,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) ) {
/* replace class instance method, added if selector not exist */
/* for class cluster , it always add new selector here */
class_replaceMethod(cls,
newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
/* swizzleMethod maybe belong to super */
class_replaceMethod(cls,
newSelector,
class_replaceMethod(cls,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)),
method_getTypeEncoding(originalMethod));
}
}
@implementation NSObject (LGKC)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/* init方法 */
swizzleInstanceMethod(objc_getClass("NSObject"), @selector(forwardInvocation:), @selector(hookforwardInvocation:));
/* init方法 */
swizzleInstanceMethod(objc_getClass("NSObject"), @selector(methodSignatureForSelector:), @selector(hookMethodSignatureForSelector:));
/* init方法 */
swizzleInstanceMethod(objc_getMetaClass("NSObject"), @selector(forwardInvocation:), @selector(hookforwardInvocation:));
/* init方法 */
swizzleInstanceMethod(objc_getMetaClass("NSObject"), @selector(methodSignatureForSelector:), @selector(hookMethodSignatureForSelector:));
});
}
- (NSMethodSignature *)hookMethodSignatureForSelector:(SEL)aSelector{
NSMethodSignature* ms = [self hookMethodSignatureForSelector:aSelector];
if (!ms) {
NSString* info = [NSString stringWithFormat:@"NSObjectSafe hookMethodSignatureForSelector [%@] sent to %@", NSStringFromSelector(aSelector), NSStringFromClass(self.class)];
NSLog(@"%@",info);
if (class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:))
!= class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:)) ){
//这里判断下,是否self实现了这个methodSignatureForSelector,实现了我们就不做处理了
//methodSignatureForSelectorw对应是交换过后的,如果不相等,self里面那还有个methodSignatureForSelector对应一个自己实现的IMP
return nil;
}
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [self hookMethodSignatureForSelector:aSelector];
}
- (void)hookforwardInvocation:(NSInvocation *)invocation{
NSString* info = [NSString stringWithFormat:@"NSObjectSafe unrecognized selector [%@] sent to %@", NSStringFromSelector(invocation.selector), NSStringFromClass(self.class)];
NSLog(@"%@",info);
//这个invocation不用触发也行的,这里写你的收集错误日志啥的
}
+ (NSMethodSignature *)hookMethodSignatureForSelector:(SEL)aSelector{
NSMethodSignature* ms = [self hookMethodSignatureForSelector:aSelector];
if (!ms) {
NSString* info = [NSString stringWithFormat:@"NSObjectSafe hookMethodSignatureForSelector [%@] sent to %@", NSStringFromSelector(aSelector), NSStringFromClass(self.class)];
NSLog(@"%@",info);
if (class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:))
!= class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:)) ){
//这里判断下,是否self实现了这个methodSignatureForSelector,实现了我们就不做处理了
//methodSignatureForSelectorw对应是交换过后的,如果不相等,self里面那还有个methodSignatureForSelector对应一个自己实现的IMP
return nil;
}
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [self hookMethodSignatureForSelector:aSelector];
}
+ (void)hookforwardInvocation:(NSInvocation *)invocation{
NSString* info = [NSString stringWithFormat:@"NSObjectSafe unrecognized selector [%@] sent to %@", NSStringFromSelector(invocation.selector), NSStringFromClass(self.class)];
NSLog(@"%@",info);
//这个invocation不用触发也行的,这里写你的收集错误日志啥的
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// [[[LGPerson alloc] init] run];
[[LGPerson alloc] run];
[LGPerson walk];
}
return 0;
}
2019-07-09 21:11:02.509572+0800 01-Runtime 初探[99399:13947205] NSObjectSafe -hookMethodSignatureForSelector [run] sent to LGPerson
2019-07-09 21:11:02.510199+0800 01-Runtime 初探[99399:13947205] NSObjectSafe -hookforwardInvocation unrecognized selector [run] sent to LGPerson
2019-07-09 21:11:02.510286+0800 01-Runtime 初探[99399:13947205] NSObjectSafe +hookMethodSignatureForSelector [walk] sent to LGPerson
2019-07-09 21:11:02.510329+0800 01-Runtime 初探[99399:13947205] NSObjectSafe +hookforwardInvocation unrecognized selector [walk] sent to LGPerson
这样程序并不会崩溃,也拦截到了导致崩溃的信息
源码网盘地址:https://pan.baidu.com/s/1aASdT6pKX-lFlpgH7o1q6w