【iOS底层】10:消息动态决议

一、方法实现缺失报错

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHelloPlus;

- (void)sayPerson;
+ (void)sayPersonPlus;
@end

@implementation LGPerson
- (void)sayPerson{
    NSLog(@"%s",__func__);
}
+ (void)sayPersonPlus{
    NSLog(@"%s",__func__);
}
@interface LGTeacher : LGPerson
- (void)sayTeacher;
+(void)sayTeacherPlus;
@end

- (void)sayTeacher {
    NSLog(@"%s",__func__);
}
+(void)sayTeacherPlus {
    NSLog(@"%s",__func__);
}
LGTeacher *teacher = [LGTeacher alloc];
[teacher sayTeacher];
[teacher sayPerson];
[teacher sayHello];

打印输出:

-[LGTeacher sayTeacher]
-[LGPerson sayPerson]
-[LGTeacher sayHello]: unrecognized selector sent to instance 0x1006479d0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGTeacher sayHello]: unrecognized selector sent to instance 0x1006479d0'

可以看到LGTeachersayTeacherLGPersonsayPerson都正常输出,LGPerson里的sayHello因为没有实现,报错了。

试一下调用类方法呢?

LGTeacher *teacher = [LGTeacher alloc];
[LGTeacher sayTeacherPlus];
[LGTeacher sayPersonPlus];
[LGTeacher sayHelloPlus];

打印输出: 

+[LGTeacher sayTeacherPlus]
+[LGPerson sayPersonPlus]
[LGTeacher sayHelloPlus]: unrecognized selector sent to class 0x100008388

同样 子类,父类实现了方法的都正常打印,父类未实现的方法报错。

来探索下为什么会报这个错误,报错流程是怎么走的。

二、报错流程探索

调用方法相当于是找方法的实例,也就是调用class_getInstanceMethod这个方法。

lookUpImpOrForward方法就是我们上篇文章分析的慢速查找流程,但是我们没有分析最终未查找到的处理。

这里break掉了死循环,然后最终返回imp,看下这里的forward_imp。

 

全局搜索_objc_msgForward_impcache,又找到了汇编里。

TailCallFunctionPointer我们探索过,就是返回$0,也就是x17。

 

x17指向的是__objc_forward_handler方法,全局搜索

 

最终找到了这里,看到了报错的元凶,而且里面连 '+' ' -'号都写好了。~ 

三、动态方法决议

上边最终找不到方法会报错,那么报错之前有没有进行什么补救措施,或者进行一些其他处理呢?来探索一下~

lookUpImpOrForward这个方法中这段代码我们没研究过,来看下: 

先来看判断条件:LOOKUP_RESOLVER = 2; 

behavior = 1 | 2,  0001 | 0010 = 0011 = 3

 所以判断条件为 3 & 2 (0011 & 0010 = 0010) = 2;

behavior ^= LOOKUP_RESOLVER;(2 ^2);behavior = 0,那么下次进行判断的话,判断条件就成了0,相当于一个单例,只走一次。

接下来看返回方法:resolveMethod_locked(inst, sel, cls, behavior);

resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        //实例方法,看这里
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    //这里为什么又查找一遍呢,因为上边if else又做了一些补救措施查找imp,确保程序不会crash
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

既然找不到以后系统会发送消息给resolveInstanceMethod方法,那么我们是不是可以重写下该方法,来赋值imp,以防crash呢。试一试。

因为消息是发送给cls,所以我们重写要写类方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
       
    NSLog(@"resolveInstanceMethod:%@--%@", self, NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

 然后我们在main方法中调动sayHello

LGTeacher *teacher = [LGTeacher alloc];
[teacher sayHello];

输出结果:

因为还是没找到,还是报错,但是报错之前打印了我们重写方法的NSLog,证明我们拦截成功了~ 至于为什么输出2次,这个我们后续再分析

那么是不是可以在她寻找不到imp的时候动态的给sel插入一个相应的imp呢,来解决这个crash。 

- (void)saveU {
    NSLog(@"666-%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if(sel == @selector(sayHello)) {
        //补救 - 动态添加一个方法
        IMP imp = class_getMethodImplementation(self, @selector(saveU));
        Method m = class_getInstanceMethod(self, @selector(saveU));
        const char *type = method_getTypeEncoding(m);
        
        return class_addMethod(self, sel, imp, type);
    }
    
    NSLog(@"resolveInstanceMethod:%@--%@", self, NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

来输出一下: 

 

完美解决~原本调用sayHello,未找到让我们修改为了saveU

同样的,如果是类方法imp缺失呢?

存在类方法里,那么元类里的对象方法不就是类里面的类方法吗,那么我们同样可以在LGTeacher类里通过实现resolveClassMethod:方法来规避crash。

+ (void)saveUPlus {
    NSLog(@"888-%s",__func__);
}


+ (BOOL)resolveClassMethod:(SEL)sel {
    
    if (sel == @selector(sayHelloPlus)){
        IMP imp = class_getMethodImplementation(objc_getMetaClass(class_getName(self)), @selector(saveUPlus));
        Method m = class_getInstanceMethod(objc_getMetaClass(class_getName(self)), @selector(saveUPlus));
        const char *type = method_getTypeEncoding(m);

        return class_addMethod(objc_getMetaClass(class_getName(self)), sel, imp, type);
    }
    
    NSLog(@"resolveClassMethod:%@--%@", self, NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

打印输出:

   

可以LGTeacher里实现resolveClassMethod方法一样可以拦截错误。

四、疑问

上边看的resolveMethod_locked(inst, sel, cls, behavior);方法中的代码里,下图这个地方为什么又要查找一次实例方法呢?

这相当于是遵循了isa流程图的两条线。

如果是非元类的,走红线的流程查找,如果是元类的,走路线查找流程。

五、imp查找失败统一拦截

上边实例方法和类方法的imp查找失败拦截我们用2个方法负责拦截的,那么这两个方法可不可以统一到一起呢?

答案是阔以的~ 通过上边我们分析if else的流程,结合isa走位图发现最终都会查找到NSObject里,放到NSObject的category里就好了~ 因为lookUpImpOrForward方法里的死循环中

这段代码就是不断的去父类里查找imp,找不到的话最终都会找到NSObject里,所以放到NSObject里总会找到。

那么我们统一一下,在NSObject里就不分类方法了,通通是实例方法,所以都会走到
+(BOOL)resolveInstanceMethod:方法里。

@interface NSObject (SaveCrash)

@end

@implementation NSObject (SaveCrash)
- (void)saveU {
    NSLog(@"666-%s",__func__);
}
+ (void)saveUPlus {
    NSLog(@"888-%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if(sel == @selector(sayHello)) {
        //补救 - 动态添加一个方法
        IMP imp = class_getMethodImplementation(self, @selector(saveU));
        Method m = class_getInstanceMethod(self, @selector(saveU));
        const char *type = method_getTypeEncoding(m);

        return class_addMethod(self, sel, imp, type);
    } else if (sel == @selector(sayHelloPlus)){
        IMP imp = class_getMethodImplementation(objc_getMetaClass(class_getName(self)), @selector(saveUPlus));
        Method m = class_getInstanceMethod(objc_getMetaClass(class_getName(self)), @selector(saveUPlus));
        const char *type = method_getTypeEncoding(m);

        return class_addMethod(self, sel, imp, type);
    }
    
    NSLog(@"resolveInstanceMethod:%@--%@", self, NSStringFromSelector(sel));
    
    return NO;
}
LGTeacher *teacher = [LGTeacher alloc];
[teacher sayHello];
[LGTeacher sayHelloPlus];

正常打印我们自己写的imp方法,成功 !

六、AOP & OOP

AOP是OOP的一个延伸,什么是OOP呢?

OOP即面向对象程序设计(Object Oriented Programming),是一种以对象为基础的编程思想。主要关注“谁来做”,即完成任务的对象。

程序中我们的对象是分工非常明确的,那么其中可能有一些冗余代码,对象A、B、C都要执行x方法,我们可以将A,B,C中的x方法提取出来放到新类M中,A,B,C都引用M来调用x方法即可,但这种方法A,B,C都必须引入M类,属于强依赖强耦合。

AOP即面向切面编程(Aspect Oriented Programming),基于OOP延伸出来的编程思想。主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

这里AOP就像我们创建的NSObject(SaveCrash),无侵入,不需要额外引入,低耦合。

但是AOP也有缺点

 像输出放在if前边,我们可以看到打印结果,除了打印了666,888我们拦截的两个方法,还打印了其他的一些系统方法,浪费了一些性能。那么系统最终做了什么处理呢,我们下一篇来探索一下苹果的另一个方式‘消息转发’~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值