消息机制

OC中的方法调用,其实都是转换为objc_msgSend函数的调用

objc_msgSend执行流程大致分为三步:

  1. 消息发送
  2. 动态方法解析
  3. 消息转发

1.消息发送

首先创建命令行工程文件,创建MessageClass类

消息机制-创建文件.png

添加messageTest方法,并且在main中调用

#import <Foundation/Foundation.h>
#import "MessageClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MessageClass *message = [[MessageClass alloc] init];
        [message messageTest];
    }
    return 0;
}
复制代码

将OC代码转换为C/C++代码:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
复制代码

这里将main.m文件转换为C++文件,并找到核心代码:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MessageClass *message = ((MessageClass *(*)(id, SEL))(void *)objc_msgSend)((id)((MessageClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MessageClass"), sel_registerName("alloc")), sel_registerName("init"));
        
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)message, sel_registerName("messageTest"));
    }
    return 0;
}
复制代码

可以直接看到转化后的方法调用代码:

((void (*)(id, SEL))(void *)objc_msgSend)((id)message, sel_registerName("messageTest")); 

objc_msgSend((id)message, sel_registerName("messageTest")); // 简化强制转换的代码
复制代码

由此也印证文章开头的OC中的方法调用,其实都是转换为objc_msgSend函数的调用

现在我们下载runtime源码(下载最新版本源码),查看objc_msgSend函数的相关实现,根据苹果注释我们粗略了解下实现源码:

ENTRY _objc_msgSend  
	UNWIND _objc_msgSend, NoFrame
        // p0寄存器: 消息接受者 receiver
	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS     // 跳转到 NilOrTag判断.
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
        // CacheLookup 缓存查找.
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:    // NilOrTag判断,判断是否为空.
	b.eq	LReturnZero		// nil check
	GetTaggedClass
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret             // 可以理解为 return.

	END_ENTRY _objc_msgSend
复制代码

查找方法:

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    Class curClass;
    IMP methodPC = nil;
    Method meth;
    bool triedResolver = NO;

    methodListLock.assertUnlocked();

    // Optimistic cache lookup
    if (behavior & LOOKUP_CACHE) {
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) goto out_nolock;
    }

    // Check for freed class
    if (cls == _class_getFreedObjectClass())
        return (IMP) _freedHandler;

    // Check for +initialize
    if ((behavior & LOOKUP_INITIALIZE)  &&  !cls->isInitialized()) {
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, initializeNonMetaClass 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
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    // be added but ignored indefinitely because the cache was re-filled 
    // with the old value after the cache flush on behalf of the category.
 retry:
    methodListLock.lock();

    // Try this class's cache.

    methodPC = _cache_getImp(cls, sel);
    if (methodPC) goto done;

    // Try this class's method lists.

    meth = _class_getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, cls, meth, sel);
        methodPC = method_getImplementation(meth);
        goto done;
    }

    // Try superclass caches and method lists.

    curClass = cls;
    // 遍历查找.
    while ((curClass = curClass->superclass)) {
        // Superclass cache.
        meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
        if (meth) {
            if (meth != (Method)1) {
                // Found the method in a superclass. Cache it in this class.
                log_and_fill_cache(cls, curClass, meth, sel);
                methodPC = method_getImplementation(meth);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }

        // Superclass method list.
        meth = _class_getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, curClass, meth, sel);
            methodPC = method_getImplementation(meth);
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if ((behavior & LOOKUP_RESOLVER)  &&  !triedResolver) {
        methodListLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    _cache_addForwardEntry(cls, sel);
    methodPC = _objc_msgForward_impcache;

 done:
    methodListLock.unlock();

 out_nolock:
    if ((behavior & LOOKUP_NIL) && methodPC == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return methodPC;
}

复制代码

objc_msgSend消息发送阶段总结:

消息机制.png
如果是从class_rw_t中查找方法

  • 已经排序的,二分查找.
  • 没有排序的,遍历查找.

receiver通过isa指针找到receiverClass

receiverClass通过superClass指针找到superClass

2.动态解析

从上面消息发送阶段可以知道,一旦消息发送阶段找不到方法,将会进入动态方法解析.

消息机制-动态解析.png
接下来我们来验证这个过程:

首先我们注释掉示例方法实现
消息机制-动态解析-代码.png
然后运行,提示经典报错:

-[MessageClass messageTest]: unrecognized selector sent to instance 0x100530150
复制代码

我们可以利用+resolveInstanceMethod:+resolveClassMethod: 动态添加方法.下面是官方解释:

消息机制-动态解析-方法解释.png
按照示例,我们动态添加方法:

消息机制-动态解析-添加方法.png
编译运行,可以发现正常运行,打印 customMethod.

3.消息转发

消息机制-7.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享