Title不重要
  • 初衷
  • 教程
    • Aria2教程
    • Mac重装教程
    • Hexo教程
    • v2ray教程
    • 翻墙教程
    • Flutter教程
    • Cocoapods教程
  • 混合框架
    • 方案一:原生+H5
      • a、Cordova
      • b、Ionic
      • c、微信小程序
    • 方案二:原生+JavaScript
      • a、React Native
      • b、Weex
      • c、快应用
    • 方案三:原生+自绘
      • a、QT for mobile
      • b、Flutter
  • 开发语言
    • Flutter
      • Dart
    • iOS
      • 1、Swift
      • 2、Objective-C
    • Android
      • Java
    • 2、Python
    • 3、JavaScript
    • 4、PHP
    • 5、Go
  • 开发者
    • 开发者个人
      • iOS
    • 开发者团队
      • 滴滴出行团队
      • 携程团队
      • Yelp团队
      • 大众点评-美团点评团队
  • 工具
    • Mac
      • 重装Mac
    • Windows
  • 干货
    • iOS干货
      • 音视频
      • 动画
      • 直播
      • Xcode
      • iOS 网站论坛
      • iOS 开源项目
        • Swift
      • iOS 视图控件
    • 书籍
      • 教育类
    • 其他干货
      • 设计干货
      • 电影网站
      • Telegram资源
    • Java干货
    • Android干货
      • Android 开源项目
  • 集成平台
    • 第三方支付
      • 微信支付
    • 高德地图
      • AMapSearch库
  • SDKS
    • iOS
      • Foundation
        • NSURLSession
  • 互联网概念
    • 后端
      • 黑石物理服务器
      • Redis
      • 360 Pika项目
      • MapReduce
      • Apache Kylin
      • Gears
      • 集群框架Spark
      • ACM国际大学生程序设计竞赛
      • Presto
      • Apache Storm
      • Apache Flink
      • Apache HBase
      • Apache Mesos
      • Apache Hadoop
      • Apache Hive
      • Apache Cassandra
      • LAMP
      • MySQL
      • 概念:微服务
    • 前端
      • iOS
        • 怎么知道一个NSObject对象占用多少内存?
        • Token过期的处理方案
        • 无侵入埋点
        • 电量优化
        • 介绍多线程
        • 介绍消息传递和转发
        • 介绍Runloop
        • 介绍Runtime
        • 介绍深拷贝和浅拷贝
        • 介绍KVO和KVC
        • 自定义的类,如何实现copy
        • 介绍Autorelease对象什么时候释放?
        • 介绍App卡顿
        • 介绍NSCoding协议和NSCopying协议
        • 介绍内存泄漏和内存溢出
        • 介绍Weak和Assign的区别
        • 介绍Delegate为什么用Weak
      • Android
        • Android中startService和bindService的区别
        • Android与vue js互相调用
      • 原理:直播流程
  • 互联网大会
    • QCon全球软件开发大会
    • AICon全球人工智能与机器学习大会
    • ArchSummit全球架构师峰会
    • GMTC全球大前端技术大会
      • 2019 GMTC
    • WWDC全球苹果开发者大会
      • WWDC2019
        • SwiftUI
      • WWDC2017
        • Universal Links
      • WWDC2018
      • WWDC资源
    • 谷歌I/O大会
  • 生活的智慧
    • 权谋学
Powered by GitBook
On this page
  • 第一部分:从方法调用得出的objc_msgSend消息传递的结论
  • 第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret
  • 第三部分:消息转发_objc_msgForward
  • 第四部分 respondsToSelector、instancesRespondToSelector
  • 第五部分 methodForSelector、instanceMethodForSelector

Was this helpful?

  1. 互联网概念
  2. 前端
  3. iOS

介绍消息传递和转发

第一部分:从方法调用得出的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
Previous介绍多线程Next介绍Runloop

Last updated 5 years ago

Was this helpful?

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