日志辅助
- 通过
lookUpImpOrForward-->log_and_fill_cache-->logMessageSend,进入logMessageSend看到源码的实现
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
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;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
/tmp/msgSends是日志保存的沙盒路径,开启以后直接到沙盒路径下就能获取文件。默认的objcMsgLogEnabled=false所以要找到赋值的地方
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
- 通过
instrumentObjcMessageSends给objcMsgLogEnabled赋值,所以在需要日志信息的地方声明instrumentObjcMessageSends既extern void instrumentObjcMessageSends(BOOL flag);
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NBPerson *nb = [NBPerson alloc];
instrumentObjcMessageSends(YES);
[nb sayHello];
instrumentObjcMessageSends(NO);
}
return 0;
}
- 这个时候我们发现报错而且查找文件里面没内容
- 遇到问题不要慌
消息转发
- 消息发送在经过动态方法决议仍然没有查找到正真的方法实现,此时动态方法决议抛出
imp=forward_imp进入消息转发流程。转发流程分两步快速转发和慢速转发
快速转发
forwardingTargetForSelector
打开xcode command + shift +0,然后全局搜索forwardingTargetForSelector
forwardingTargetForSelector含义是返回未识别消息重定向的对象,简单理解指定一个对象,让这个对象去接收这个消息
实例探究
定义NBPerson类不实现sayHello
@interface NBPerson : NSObject
-(void)sayHello;
@end
@implementation NBPerson
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(sayHello)) {
return [[NBTeacher alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
定义NBTeacher类事项sayHello
@interface NBTeacher : NSObject
@end
@implementation NBTeacher
-(void)sayHello
{
NSLog(@"---%s---",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NBPerson *nb = [NBPerson alloc];
[nb sayHello];
}
return 0;
}
- 打印结果显示:
NBTeacher类和NBPerson类没有任何关系,但是指定给NBTeacher类对象,仍然最后可以查询到,并且没有崩溃消息,其实消息在查询过程中先去继承链中去查找,最后没找到。于是系统把这个权限丢给开发者,让开发者直接指定对象和类能接收这个消息。
慢速转发
methodSignatureForSelector的含义是返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。methodSignatureForSelector一般搭配和forwardInvocation使用,如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation
@implementation NBPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@---",anInvocation);
}
@end
//此时methodSignatureForSelector的返回值是nil,慢速转发完成,直接闪退
@implementation NBPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
}
@end
//输出结果
2021-08-27 19:33:53.645285+0800 KCObjcBuild[64750:1211770] ---<NBPerson: 0x100b1e3d0>---sayHello
- 如果
methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation进行实物处理anInvocation保存了NSMethodSignature签名信息,还有目标方法的方法签名sel,以及方法的接收者。此时不会报奔溃信息,当然也可以处理anInvocaion事务
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NBTeacher * teacher = [NBTeacher alloc];
anInvocation.target = teacher;
anInvocation.selector = @selector(sayHello);
[anInvocation invoke];
}
//打印结果
2021-08-27 19:43:16.418571+0800 KCObjcBuild[65158:1217835] ----[NBTeacher sayHello]---
- anInvocation.target是teacher对象anInvocation.selector是teacher的sayHello方法
- [anInvocation invoke]触发消息的调用
结论
- 快速转发:通过
forwardingTargetForSelector实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil,进入慢速转发流程 - 慢速转发:通过
methodSignatureForSelector和forwardInvocation共同实现,如果methodSignatureForSelector返回值是nil,慢速查找流程结束,如果有返回值forwardInvocation的事务处理不处理都不会崩溃