消息转发机制
消息的查找流程分为:快速查找和慢速查找
消息转发机制也分为:快速和慢速
先来一个转发流程图
之前我们的消息查找流程中有这端代码
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
如果resolver
为true,triedResolver
为false,那么就会走_class_resolveMethod
oid _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);
}
}
}
首先判断当前类是否为元类,如果是元类就意味着为类方法,否则就是实例方法.
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
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
现在发送一个SEL_resolveInstanceMethod的消息,SEL_resolveInstanceMethod
是个什么东西呢
来看下注释 * Call +resolveInstanceMethod, looking for a method to be added to class cls.
他是一个+ resolveInstanceMethod
方法,那么我本类中没有实现resolveInstanceMethod
这个方法啊为啥他就不蹦呢,因为他的父类中实现了,我们搜一下发现NSObject中实现了
,那么我们再来看一下上边的判断条件
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
这里是做了一个容错处理,因为有可能你的类没有继承NSObject,那就意味着找不到该方法,就没有必要再去发送消息,然后根据上边我们讲的消息查找流程,去父类中找最后找到NSObject中,所以这里不会出现崩溃
调用了一个只声明没有实现的方法直接崩溃,而且崩溃前会先调用一下resolveInstanceMethod
.
那么怎么去挽救一下呢.
来重写resolveInstanceMethod.之所以崩溃是因为没有找到imp,那么我们可以给他加一个imp
//动态方法决议
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"来了老弟");
//如果注释掉下边`{}`中代码,发现这里会来两次,打印两次来了老弟
//
if (sel == @selector(saySomething)){
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method method = class_getInstanceMethod(self, @selector(sayHello));
const char *sayType = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayHIMP, sayType);
}
return [super resolveInstanceMethod:sel];
}
完美解决崩溃问题
如果这里没有做特殊处理,返回的是一个NO
,那么会来到一个快速转发流程,
快速转发流程
- 首先来看一下打印
我们忽略了一个函数log_and_fill_cache
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
如果objcMsgLogEnabled
为true则打印,点击objcMsgLogEnabled
看下他的实现
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;
}
这里有一个文件路径.那么我们只需要将objcMsgLogEnabled
设置为true就可以看到打印信息了.
我们发现下边这个函数instrumentObjcMessageSends
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;
}
我们只需要调用这个函数传入一个true
就ok
了,比如
extern void instrumentObjcMessageSends(BOOL);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [[LGStudent alloc] init];
instrumentObjcMessageSends(YES);
[student saySomething];
instrumentObjcMessageSends(NO);
}
return 0;
}
然后打开/private/tmp
文件目录
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class
看到他调用了forwardingTargetForSelector
看一下文档
Discussion
If an object implements (or inherits) this method, and returns a non-nil (and non-self) result,
that returned object is used as the new receiver object and the message dispatch resumes
to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)
大致意思就是如果自己没有实现可以返回一个新的实现了改方法的对象
那么如果动态方法决议没有处理,那么来到这个方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
我们这里返回一个teacher没让他来实现这个方法,那么如果他也没有实现呢,就会来到下一个消息转发流程
慢速转发流程
慢速转发
//根据selector生成一个NSMethodSignature方法签名并返回
//方法签名其实就是,方法的参数返回值类型的一些信息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- 使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
方法签名TypeEncoding
那么只有签名没有实现是不行的
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [LGStudent alloc] ;
// 动态方法决议
// 对象方法
// 类方法 -
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
@implementation LGStudent
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
// 事情 - 事务 - 秘书 - 失效
// 系统本质
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
@end
如上,如果student调用一个没有实现的方法,那么回来到慢速转发流程,返回签名之后,我们把这个消息指派给实现了saySomething
方法的teacher
类,如果没有指派,那么直接崩溃.至此我们的消息转发流程走完
利用消息转发预防崩溃方案
上边讲了,runtime
给出的三种补救方式
调用
resolveInstanceMethod
给个机会让类添加这个实现这个函数调用
forwardingTargetForSelector
让别的对象去执行这个函数调用
forwardInvocation
(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector
抛出异常。选择哪一种合适呢?
1.resolveInstanceMethod
需要在类上动态添加他本身不存在的方法,这些方法对于类本身来说是冗余的,不合适
-
forwardInvocation
可以通过NSInvocation
的形式将消息转发给多个对象,但是其开销大,需要创建NSInvocation
对象,并且forwardInvocation
的函数经常被使用者调用,来做多层消息转发机制,不适合多次重写 -
fowardIngTargetForSelector
可以将消息转发给一个对象,开销小,并且被重写的概率较低,适合重写
这里要注意,如果原本类已经重写了forwardInvocation
的话,就能对forwardingTargetForSelector
进行重写了,这样会影响对象原本的消息转发
如何做?
- 首先动态创建一个类
- 给类动态添加对应的selector
- 将消息转发给这个类对象上