补充
initialize
lookUpImpOrforward函数中会调用realizeAndInitializeIfNeeded_locked -> realizeClassMaybeSwiftAndLeaveLocked,即类的initialize方法 ```objectivec static Class realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize) { runtimeLock.assertLocked(); if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath(initialize && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
} return cls; }
<a name="uO41r"></a>
## unrecognized selector
- 方法找不到时,报经典错误unrecognized selector sent to instance 0x...........
- 其调用流程为:
- lookUpImpOrForward(初始赋值forward_imp = (IMP)_objc_msgForward_impcache)
- 方法未找到时,imp = _objc_msgForward_impcache, _objc_msgForward_impcache被赋值给了imp
- _objc_msgForward_impcache汇编中调用了objc_defaultForwardHandler方法
- objc_defaultForwardHandler中执行了格式打印
```objectivec
__attribute__((noreturn, cold)) void
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);
}
instrumentObjc函数
在lookupImpOrForward函数中,done:结束后调用了log_and_fill_cache,该函数中有一个判断是否支持写入messageLog,如果支持且开启,则可以在对应文件夹中找到消息发送的日志文件
log_and_fill_cache(cls, imp, sel, inst, curClass);
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
///<< implementer为传入的implementer,在lookupImpOrForward函数中,传入的是curClass为真
///<< 所以此时objcMsgLogEnabled如果为真,则可以开启写入流程
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
///<< logMessageSend写文件的地址为 "/tmp/msgSends-%d"
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
...
}
建立person类,在对应想打印的方法前后添加调用
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
编译后在对应的temp文件夹下找到了msgSends-70902文件
msgSends-70902文件中记录的msgSend的流程
动态消息处理
lookUpImpOrForward中的单例
LOOKUP_RESOLVER = 2, 0011 & 0010 = 0010
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER; 异或
return resolveMethod_locked(inst, sel, cls, behavior);
}
resolveInstanceMethod
resolveInstanceMethod:(inst, sel, cls), 非元类时走这里
- 进而包装resolve…method
- resolve_sel = @selector(resolveInstanceMethod:) 实现了此方法,则进行容错处理
- bool resolved = msg(cls, resolve_sel, sel) 根据resovled是否成功进行下一步处理
- imp = looUpImpOrNilTryCache(inst, sel, cls)
- 这里走两次原因是一次根据继承链查找,一次根据isa的走位图查找,所以后续可以写在NSObject分类中统一处理(resolveInstanceMethod + resolveClassMethod) ```objectivec
- 进而包装resolve…method
(BOOL)resolveInstanceMethod:(SEL)sel { // 来了两次 // 处理 sel -> imp
IMP sayNBImp = class_getMethodImplementation(self, @selector(sayNB)); Method method = class_getInstanceMethod(self , @selector(sayNB)); const char * type = method_getTypeEncoding(method); if (sel == @selector(say666)) {
return class_addMethod(self, sel, sayNBImp, type);
} return [super resolveInstanceMethod:sel]; } ```
resolveClassMethod
- resolveClassMethod:(inst, sel, cls),元类时走这里 ```objectivec //// 元类的以对象方法的方法
(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@”resolveClassMethod :%@-%@”,self,NSStringFromSelector(sel));
if (sel == @selector(sayHappy)) {
IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);
}
return [super resolveClassMethod:sel]; } ```
AOP
- 切面编程,上述在Person类中写的resolve方法,可以统一写在NSObject分类中,根据isa的走位图和继承链,会分别调用实例方法和对象方法共两次 ```objectivec
- (void)sayNB{ NSLog(@”%@ - %s”,self , func); }
- (void)sayKC{ NSLog(@”%@ - %s”,self , func); }
pragma clang diagnostic push
// 让编译器忽略错误
pragma clang diagnostic ignored “-Wundeclared-selector”
(BOOL)resolveInstanceMethod:(SEL)sel{ // resolveInstanceMethod :LGTeacher-say666 为什么是两次 家庭作业 // 处理 sel -> imp
NSLog(@”resolveInstanceMethod :%@-%@”,self,NSStringFromSelector(sel));
if (sel == @selector(say666)) {
IMP sayNBImp = class_getMethodImplementation(self, @selector(sayNB));
Method method = class_getInstanceMethod(self, @selector(sayNB));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayNBImp, type);
}else if (sel == @selector(sayHappy)) {
IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);
} return NO; } @end
pragma clang diagnostic pop
```
- 本质就是通过say666() sel 查找imp的过程
- 苹果给的一次机会
- 写在NSObject分类中,全局方法找不到时,我们都能监听到
- 命名规范:lg_model_traffic -> lg_home_didClickDetail -> pop home -> 发送消息给后台 -> 修改为题
- 利用了Runtime特性
- oop优缺点
- 对象分工明确
- 代码冗余,某一特性,需要继承(强依赖 - 强耦合)
- aop 无入侵 - 动态注入代码,注意切入的方法以及切入的类
- 会有性能消耗,此时是消息转发前的最后一次,后面的消息转发无意义,所以这里NSObject做Aop不好
消息转发
消息转发分为快速转发和慢速转发,其实现是在CoreFoundation框架。
forwardingTargetForSelector
快速转发
- 我给你,要不起,转给别人,找背锅侠
- 所有方法找不到,都向Student类中堆积,看是否会实现
建立一个person类,一个student类,teacher声明一个say方法且未实现,在person中有对应声明和实现,运行时会报错
LGPerson *person = [LGPerson alloc];
[person sayHello];
在person类中加入forwardingTargetForSelector方法,则消息转发给了student处理
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [LGStudent alloc];
}
-[LGStudent sayHello]
methodSignatureForSelector
- 慢速转发
- methodSignatureForSelector 要和forwarInvocation配套使用才能生效,在methodSignatureForSelector方法签名后,系统会保存此方法。
- methodSignature判断sel,进行签名
- forwardInvocation处理
- 签名即”V@:@”格式
- 所有系统消息都称为事务,可做可不做。所以在methodSignature签名后,forwardInvocation中不处理也不会崩溃 ```objectivec
(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@”%s - %@”,func,NSStringFromSelector(aSelector)); if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
} return [super methodSignatureForSelector:aSelector]; }
(void)forwardInvocation:(NSInvocation )anInvocation{ NSLog(@”%@ - %@”,anInvocation.target,NSStringFromSelector(anInvocation.selector)); LGStudent s = [LGStudent alloc]; if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invoke];
}else if ([s respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:s]; // s来接
}else{
NSLog(@"%s - %@",__func__,NSStringFromSelector(anInvocation.selector));
流程探索
去除上述快速转发、慢速转发消息的处理,运行时崩溃在方法找不到,这是bt查看函数调用栈,发现CoreFoundation框架中的-[NSObject(NSObject) doesNotRecognizeSelector:] 与当前程序崩溃信息一致,其向前调用函数分别为forwarding和_CF_forwarding_prep_0
CoreFoundation静态库部分开源,查找不到forwarding和_CF_forwarding_prep_0,所用需要用CoreFoundation的动态库查看
- 通过hopper查看分析coreFoundation
未实现forwardingTargetForSelector则跳转loc_64a67
流程继续进行,则会判断是否实现了forwardInvocation
- 动态决议resolveInstanceMethod走两次的原因:
- 第一次方法没有此方法,会调用resolveInstanceMethod方法
- 通过bt查看堆栈信息,第二次则为
- _CF_forwarding_prep_0
- forwarding
- [NSObject(NSObject) doesNotRecongnizeSelector:]
- class_respondsToSelector_inst
- lookUpImpOrNilTryCache