介绍消息传递和转发

第一部分:从方法调用得出的objc_msgSend消息传递的结论

1.1 文字的描述:先来看一段代码:

  1. ClangClass类有个handle方法

  2. handle方法里面是创建AClass对象并调用addO方法

  3. 我们来看看调用addO方法的具体过程

1.2 源代码的描述:

#import "ClangClass.h"
#import "AClass.h"

@interface ClangClass()
@property(nonatomic,strong)AClass *acClass;
@end

@implementation ClangClass

- (instancetype)init{
    if (self = [super init]) {
        _acClass = [[AClass alloc] init];
    }
    
    return  self;
}

- (void)handle{
    [_acClass addO];
}

@end

1.3 将代码操作一下:

  1. 进入到上面ClangClass.m的文件夹下面,使用命令编译,命令为:clang -rewrite-objc ClangClass.m

  2. 看文件ClangClass.cpp得到的结果为,我们具体看调用的代码是:[_acClass addO];

  3. {是不是感觉一大堆很乱,我也觉得}

static void _I_ClangClass_handle(ClangClass * self, SEL _cmd) {
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)(*(AClass **)((char *)self + OBJC_IVAR_$_ClangClass$_acClass)), sel_registerName("addO"));
}

// 把上面代码优化下,去掉强制转换的符号得到
objc_msgSend(_acClass, sel_registerName("addO"));

---------------------------------------------

IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
    typedef id

得出结论:

  1. 调用方法addO就是调用objc_msgSend进行消息传递;

  2. 而objc_msgSend就是去找到IMP,来执行实现的代码;

第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret

objc_msgSend定义:id objc_msgSend(id self, SEL op, …);

self:A pointer that points to the instance of the class that is to receive the message.{消息的接收者}
op:The selector of the method that handles the message.{是selector}
 …:A variable argument list containing the arguments to the method.{是参数列表}

id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类

typedef struct objc_object *id;
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
}

SEL的本质:SEL 是系统在编译过程中,根据方法的名字以及参数序列生成一个用来区分这个方法的唯一ID编号;这个ID就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。

Selector(typedef struct objc_selector *SEL)

2.1 文字描述objc_msgSend的工作流程:

  1. 在self中沿着isa找到AClass的类对象;

  2. 优先在AClass的cache查找addO方法,如果有就返回imp;

  3. 如果第一次没找到缓存中的addO方法,就查看这个类有没有被释放,如果释放了,就返回;

  4. 如果这个类没有被释放,就检查这个类有没有实现 +initialize方法,如果有但是没有初始化,就初始化;

  5. 判断初始化后,尝试再在AClass的cache查找addO方法,如果找不到,就到AClass的methodLists查找addO方法;

  6. 如果没有在AClass找到addO方法,就会到super_class就是NSObject里面的cache查找;如果在父类的cache找不到,就到父类的methodLists中查找addO方法;

  7. 如果在父类的methodLists也没有找到addO方法,且如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了。

  8. 如果resolver设置为NO,且还没有找到IMP,就返回_objc_msgForward_impcache开始消息转发;

  9. 不管上面的哪一步找到imp,都直接返回imp。

2.2 相关:

  • _objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。

  • 当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

2.3 本质:

  • objc_msgSend的本质就是找到imp指针执行代码,而静态语言的imp函数指针在编译的时候就已经确定好了,动态语言是编译后可以变换的。

2.4 源代码描述objc_msgSend的工作流程:

{其实我没有找到这段的实现,就用大神的代码了}
id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //调用这个函数,伪代码...
}

// 源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        // 如果找不到imp就返回消息转发
        return _objc_msgForward;
    }

    return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    // 查找imp或决定转发
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

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

    methodListLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        // 查找imp,如果有缓存,就返回缓存
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) return methodPC;    
    }

    // Check for freed class
    // 查看释放的类
    if (cls == _class_getFreedObjectClass())
        // 如果类已经释放,返回_freedHandler
        return (IMP) _freedHandler;

    // Check for +initialize
    // 检查有没有实现+initialize方法
    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // 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
    }

    // 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 (resolver  &&  !triedResolver) {
        // 解锁
        methodListLock.unlock();
        // 如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了
        _class_resolveMethod(cls, sel, inst);
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    // 方法找不到:尝试一次resolver方法无效;开始转发
    _cache_addForwardEntry(cls, sel);
    // 返回_objc_msgForward_impcache表示转发
    methodPC = _objc_msgForward_impcache;

 done:
    // 解锁
    methodListLock.unlock();

    return methodPC;
}

第三部分:消息转发_objc_msgForward

// add1方法是不存在的,现在试试转发
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    AClass *acClass = [[AClass alloc] init];
    [acClass performSelector:@selector(add1)];
}

3.1 oc转发文字描述:

  1. 调用resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。

  2. 如果调用resolveInstanceMethod没有实现,就开始调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。

  3. 如果调用forwardingTargetForSelector没有效果,就开始调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。

  4. 将第三步获取到的方法签名包装成Invocation传入,调用forwardInvocation:方法,如何处理就在这里面了,并返回非nil。

  5. 调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。

3.2 oc转发源代码描述:

使用instrumentObjcMessageSends的方法来查看app的log信息,具体使用方法:在appdelegate.m里面定义如下:

#import "AppDelegate.h"
#import <objc/runtime.h>

void instrumentObjcMessageSends();
@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    instrumentObjcMessageSends(YES);
    return YES;
}


// 下面是消息转发的log信息
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject performSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject class
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject class

第四部分 respondsToSelector、instancesRespondToSelector

// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    AClass *acClass = [[AClass alloc] init];
    [acClass respondsToSelector:@selector(add1)];
}

4.1 文字分析:

  1. 如果没有找到imp方法,就尝试一次resolveInstanceMethod,不会进行消息转发

  2. 不会进行消息转发,所以就算找不到方法,也不会报doesNotRecognizeSelector这个找不到方法的报错了

  3. 主要的原因,看respondsToSelector的源码就知道,在下面

4.2 源码分析:

+ (BOOL)respondsToSelector:(SEL)sel {
    if (!sel) return NO;
    return class_respondsToSelector_inst(object_getClass(self), sel, self);
}

- (BOOL)respondsToSelector:(SEL)sel {
    if (!sel) return NO;
    return class_respondsToSelector_inst([self class], sel, self);
}

---------------------------------------------
+ (BOOL)instancesRespondToSelector:(SEL)sel {
    if (!sel) return NO;
    return class_respondsToSelector(self, sel);
}

---------------------------------------------
BOOL class_respondsToSelector(Class cls, SEL sel)
{
    return class_respondsToSelector_inst(cls, sel, nil);
}

bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
    IMP imp;

    if (!sel  ||  !cls) return NO;

    // Avoids +initialize because it historically did so.
    // We're not returning a callable IMP anyway.
    // 这里的resolver传入的是YES,所以当找不到方法的时候,就只会执行一次resolveInstanceMethod方法,避开了消息转发,具体看最上面的lookUpImpOrNil方法
    imp = lookUpImpOrNil(cls, sel, inst, 
                         NO/*initialize*/, YES/*cache*/, YES/*resolver*/);
    return bool(imp);
}

---------------------------------------------

// respondsToSelector消息转发部分的log
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject respondsToSelector:
- AClass NSObject class
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

第五部分 methodForSelector、instanceMethodForSelector

// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    AClass *acClass = [[AClass alloc] init];
    [acClass methodForSelector:@selector(add1)];
}
---------------------------------------------

+ (IMP)methodForSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return object_getMethodImplementation((id)self, sel);
}

- (IMP)methodForSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return object_getMethodImplementation(self, sel);
}

---------------------------------------------
+ (IMP)instanceMethodForSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return class_getMethodImplementation(self, sel);
}

5.1 看源码可以知道,是和respondsToSelector一样的实现机制:没有消息转发

+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject methodForSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51

Last updated