oc的方法最后都以objc_msgSend()方式发送。
通过代码的方式查看一下:
TestViewController *test = [[TestViewController alloc] init];
通过clang命令将当前类编译成c++文件查看:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_d037d6_mi_0);
TestViewController *test = ((TestViewController *(*)(id, SEL))(void *)objc_msgSend)((id)((TestViewController *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestViewController"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
alloc方法会被编译成:
(void *)objc_msgSend)((id)objc_getClass("TestViewController"), sel_registerName("alloc")
init方法:
((id)((TestViewController *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestViewController"), sel_registerName("alloc")), sel_registerName("init"))
objc_msgSend(receiver,SEL,arg1,arg2);
receiver:消息发送者;
SEL:方法;
arg:方法参数
objc_msgSend的发送流程
消息发送流程分为三个阶段:
1.消息发送;
2.动态解析;
3.消息转发;
消息发送机制比较好理解,这里重点说一下动态解析、消息转发:
动态方法解析:
当一个方法无法从当前类,父类中找到的时候,就会触发动态解析流程;何为动态解析:objc运行时会调用+resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现,如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程。
通过runtime源码查看实现:
// 未找到实现。尝试方法解析器一次。
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
//发生变化,重启方法查找流程
triedResolver = YES;
goto retry;
}
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _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);
}
}
}
继续查看 _class_resolveInstanceMethod(cls, sel, inst);
的实现:
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//这里查找元类的原因是因为+(void)resolveInstanceMethod 是一个类方法,而类方法是存储在元类中的
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//调用cls的+(void)resolveInstanceMethod方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
}
我们继续看一下调用+(void)resolveInstanceMethod
后,新的方法是怎么替换老的方法的:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
class_addMethod(self.class, sel, (IMP)dynamicMethodIMP, "@@:");
BOOL result = [super resolveInstanceMethod:sel];
result = YES;
return result;
}
继续查看class_addMethod
的实现:
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
addMethod
的实现:
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
assert(types);
assert(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp;
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdupIfMutable(types);
newlist->first.imp = imp;
prepareMethodLists(cls, &newlist, 1, NO, NO);
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls);
result = nil;
}
return result;
}
原来是替换了method_list_t的方法指针。
以后再次查找此方法时,就会通过lookUpImpOrForward
这段代码进行查找:
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
到了这里动态方法解析就已经做完了。如果已经实现了+(void)resolveInstanceMethod
,那么新实现的方法就会替换旧的未实现的方法,如果未实现+(void)resolveInstanceMethod
方法,那么接下来就会进入到消息转发的流程。
消息转发
当动态方法解析失败后会启动消息转发流程,消息转发,顾名思义:就是把该消息转发出去,转发给其他对象,让目标对象实现此方法,消息转发分为两类,一类是快速转发,一类是普通转发。
Fast forwarding
如果目标对象实现了 -forwardingTargetForSelector:,Runtime会在这个时候调用,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是nil或self,整个消息发送的过程就会被重启,当然发送的对象就会变成你返回的那个对象。否则就会继续Normal Forwarding。这里叫Fast,只是为了区别下一步的消息转发,因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对快点。
继续从源码了解消息转发的实现,
在之前,先看一下lookUpImpOrForward
方法的注释:
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
lookUpImpOrForward
方法:
// No implementation found, and method resolver didn't help.
// Use forwarding.
//如果没有找到方法实现,并且没有动态方法解析,使用消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
这时候的imp会变成 _objc_msgForward_impcache
,_objc_msgForward_impcache
只是一个内部的函数指针,上面的注释里提到,_objc_msgForward_impcache
需要被转换为_objc_msgForward
和 _objc_msgForward_stret
才能被外部调用,转换过程是汇编实现,而 _objc_msgForward_impcache
就是汇编实现的静态入口:
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret
beq __objc_msgForward
b __objc_msgForward_stret
END_ENTRY __objc_msgForward_impcache
至于转换称_objc_msgForward
还是_objc_msgForward_stret
是通过EQ\NE决定的:
EQ = Equal:_objc_msgForward
NE = NotEqual:_objc_msgForward_stret
此时转换完成imp指针就会被替换成_objc_msgForward\ _objc_msgForward_stret调用。
此时我们只需要在代码中实现:
- (id)forwardingTargetForSelector:(SEL)aSelector {
id result = [super forwardingTargetForSelector:aSelector];
result = self.objectTest;
return result;
}
self.objectTest一定是实现了被转发的方法,不然会继续走下一步,当此方法被实现了后,整个消息发送的过程就会被重启;
Normal forwarding
此过程比上一步稍微慢一些是因为这个过程的消息转发会创建一个NSInvocation对象,
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector返回nil,Runtime会直接发出-doesNotRecognizeSelector:消息,程序这时就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
[super methodSignatureForSelector:aSelector];
NSMethodSignature *initSignature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
return initSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if ([self.objectTest respondsToSelector:selector]) {
[anInvocation setTarget:self.objectTest];
[anInvocation invoke];
}
}
此过程的消息转发可以帮助我们轻松获取某一个方法的所有参数,比如著名的JSPatch就是通过消息转发的方法获取方法的参数,
网友评论