for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
//再一次从cache里面去找imp
//目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
在上面我们研究源码时候知道,其objc_send这个查找方法,会现在其本类里面找,如果找不到的话会去父类里找,如果还没找到会进行循环直到找到NSObject为nil的时候,这个NSObject是万类之主。如果还没找到的时候,就会进行赋值imp = forward_imp,我们点击这个forward_imp进去发现是如下代码, const IMP forward_imp = (IMP)_objc_msgForward_impcache,我们全局进行搜索下_objc_msgForward_impcache,发现其是用汇编进行写的,核心的几句代码如下:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
在所有方法都没找到的时候其会调用我们oc里这段代码,想必大家也经常能够看到。
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
同时在方法找不到的时候在其前面会调用 动态决议方法,这两个方法如下:
static NEVER_INLINE IMP
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);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
那我们来验证下,当找不到示例方法的时候会走动态决议方法,同时我们再自己实现这个方法。
首先我们在mian函数里定义了一个HPWPerson对象,调用test1这个方法,但是这个方法我们在HPWPerson类里面并没有去实现,如果不实现的话就会奔溃。所以我们在奔溃前可以进行重新resolveInstanceMethod这个方法,然后在这个方法里把我们test1方法换成method1方法调用,那这样就不会奔溃了。结果如下图控制台输出,也可以正常进行一个打印。在消息进行转换后其方法还是会放到当前类的cache里缓存,存的是method1,因为method1是imp也就是方法的实现。
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPWPerson *p = [[HPWPerson alloc] init];
[p test1];
}
return 0;
}
HPWPerson类
#import "HPWPerson.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation HPWPerson
+(BOOL)resolveInstanceMethod:(SEL)sel {
// NSLog(@"%s,%@",__func__,NSStringFromSelector(sel));
/**
const char *types
描述方法参数类型的字符数组。描述方法参数类型的字符数组的第一个字符是代表返回值的类型,后面的字符依次代表参数的类型,因为Objective-C中的函数会包含两个隐式参数,也就是方法调用者和方法名,所以类型编码的第二个字符一定是@,第三个字符一定是:
*/
class_addMethod(self.class, sel, class_getMethodImplementation(self.class, @selector(method1)), "v@:");
return YES;;
}
- (void)method1 {
NSLog(@"%s",__func__);
}
@end
运行上面后打印结果如下:
SXObjcDebug[37741:414917] -[HPWPerson method1]
上面讲的是实例方法的动态决议,接着我们来说下类方法的动态决议,类方法用到的动态决议 方法是+(BOOL)resolveClassMethod:(SEL)sel,其和我们上面也是一样的,只是其是对于类方法而已.
+(BOOL) resolveClassMethod:(SEL)sel {
// NSLog(@"%s,%@",__func__,NSStringFromSelector(sel));
/*const char *types
描述方法参数类型的字符数组。描述方法参数类型的字符数组的第一个字符是代表返回值的类型,后面的字符依次代表参数的类型,因为Objective-C中的函数会包含两个隐式参数,也就是方法调用者和方法名,所以类型编码的第二个字符一定是@,第三个字符一定是:
*/
class_addMethod(objc_getMetaClass("HPWPerson"), sel, class_getMethodImplementation(objc_getClass("HPWPerson"), @selector(method2)),"v@:");
return YES;
}
//照着苹果文档写
return NO;
}
+ (void)method2 {
NSLog(@"%s",__func__);
}
通过上面我们可以把其运用到实际运用中,进行AOP编程。
⾯向切⾯编程(AOP)在不修改源代码的情况下,通过运⾏时给程序添加统⼀功能的技术。
埋点就是在应⽤中特定的流程收集⼀些信息,⽤来跟踪应⽤使⽤的状况,然后精准分析⽤户数据。
⽐如⻚⾯停留时间、点击按钮、浏览内容等等。
1.通过上面我们在NSObject添加一个分类,如NSObject+HPW,在这个分类里进行实例方法的动态决议重写,如(BOOL)resolveInstanceMethod:(SEL)sel ,这样就不会因为方法找不到而奔溃了,因为类方法找不到和示例方法找不到都会到NSObject+HPW这个分类里。在这里可以记录哪些方法没有实现。
2.页面停留时间:思路也是在UIVIewcontroller里添加分类UIVIewcontroller+HPW,添加一个<objc/runtime.h>在这个分类里写viewWillapper和viewWillDIsappear来获取停留的时间。然后通过runtime进行方法的交换。就知道点击了哪个界面,和到哪里消失了。
下面这个代码是写在UIVIewcontroller+HPW里的。
![](https://img.haomeiwen.com/i9297435/19943207e3a5e6a4.jpeg)
消息转发
消息转发是发生在动态决议后面的:
1)消息的快速查找
当我们类调用一个方法,这个方法在这个类里没有,可以在该类里使用forwardingTargetForSelector进行消息的快速转发,转发给某个类去实现。这个方法是缓存在HPWTeacher里,因为这个方法已经转发出去了,是HPWTeacher进行调用。这个是实例方法的转发,如果是类方法的话就是+(id)forwardingTargetForSelector:(SEL)aSelector这个方法。
-(id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(method1)) {
return [HPWTeacher alloc];
}
return nil;
}
如果消息没有进行一个快速转发,就会进行一个慢速转发。我们只要添加了这个代码就不会因为方法找不到而奔溃了。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法的签名
}
-(void)forwardInvocation:(NSInvocation *)anInvocation {
}
消息转发的流程图:
![](https://img.haomeiwen.com/i9297435/6eb02f4a1b90f4ec.jpeg)
网友评论