首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >runtime的那些事(四)—— selector、IMP、Method

runtime的那些事(四)—— selector、IMP、Method

作者头像
我只不过是出来写写代码
发布于 2019-07-19 02:53:30
发布于 2019-07-19 02:53:30
2.1K00
代码可运行
举报
运行总次数:0
代码可运行

之前对 NSObject 类内部结构体做了一个基本的分析。原本是想从 runtime 层面上整理消息传递流程,但为了能够顺畅的整理知识点,决定这篇还是先整理几个非常重要的结构体概念。

目录

1. selector

2. IMP

3. Method


1. selector

 selector 是指方法选择器,在面向对象里可以理解为函数的指针。@selector() 作用就是在指定类中寻找指定名称的方法。

&emsp关于 selector 的用法,其返回类型为 SEL。关于 SEL 的定义,最权威的还是在官方文档中的解释。SEL官方文档链接

 关于官方文档对于 SEL 的声明,翻译过来大意如下:selector 方法选择器用于在运行时表示方法的名称,一个 selector 选择器其实就是已经向运行时注册或者映射过的C字符串,通过编译器生成的 selector 选择器在类加载时由运行时自动映射。允许在运行时添加新的 selector 选择器,并可以使用函数 sel_registerName 检索已有的 selector 选择器。但是在使用 selector 选择器时,必须使用函数 sel_registerName 或者 Objective-C 编译器的指令 @selector() 返回的值,而不能直接将 C字符串强制转换成 SEL。

关于 SEL 在 runtime 中的定义,在 runtime 源码中仅仅是找到了结构体的声明。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct objc_selector *SEL;

 虽然看不到关于 struct objc_selector 的内部声明,但是可以去推测内部结构。在结构体中,一定会有一个 char 类型的变量用于存储该函数名的C字符串。

 关于 selector 创建与获取,不管是创建 @selector() 、还是获取 NSSelectorFromString()method_getName(),其底层的实现都是通过 sel_registerName 函数来实现的。

关于 sel_registerName() 函数的底层实现

从 runtime 源码 objc-sel.mm 文件中找到了其定义。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SEL sel_registerName(const char *name) {
    return __sel_registerName(name, 1, 1);     // YES lock, YES copy
}

内部通过C函数 static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) 来完成实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) 
{
    SEL result = 0;

    if (shouldLock) selLock.assertUnlocked();
    else selLock.assertLocked();

    if (!name) return (SEL)0;

    result = search_builtins(name);
    if (result) return result;
    
    conditional_mutex_locker_t lock(selLock, shouldLock);
    if (namedSelectors) {
        result = (SEL)NXMapGet(namedSelectors, name);
    }
    if (result) return result;

    // No match. Insert.

    if (!namedSelectors) {
        namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, 
                                          (unsigned)SelrefCount);
    }
    if (!result) {
        result = sel_alloc(name, copy);
        // fixme choose a better container (hash not map for starters)
        NXMapInsert(namedSelectors, sel_getName(result), result);
    }

    return result;
}

 上述函数中,result SEL 类型的变量就是最终返回的结果。从源码中初步看了下,会发生四种不同的 SEL 类型结果返回情况。从上往下的顺序依次是:

  1. 当传入方法名为 nil 时,则直接返回内容为0的值;
  2. 再传入的方法名与 builtins 中的进行比对,若存在相同方法名,则直接返回 builtins 中的方法名。 (PS:此处的 builtins 作用为生成一个共享缓存,用于保存预先优化过的选择器,以此可以实现更快速地查找方法,该函数的实现是由 C++ 定义的命名空间 objc_opt 来完成。关于 builtins 的实现原理就不展开了,以后有时间再细细研究 C++ 的命名空间以及 objc_opt 的内部细节。)
  3. 若上述流程未找到,则将传入的方法名作为 key,去 NXMapTable 中去搜索 SEL 类型的结果。 NXMapTable 的作用就是将方法名与对应的 SEL 字符串进行绑定映射,并存入该哈希表中。
  4. 若上述哈希表依然没有找到,则会将当前的方法名创建新的 SEL,并将 SEL 插入至 NXMapTable 中保存与对应方法名的映射关系。同时将该方法名创建的 SEL 作为返回值返回。

创建 selector 途径有:

  • sel_registerName
  • @selector()

获取 selector 的途径有:

  • NSSelectorFromString()
  • method_getName()

通过官方文档对 NSSelectorFromString 的解释,将一个方法名的UTF-8编码字符串传给 sel_registerName 函数并返回 SEL;关于 method_getName() 函数的实现,通过 runtime 源码层面也可以发现也是通过 sel_registerName 来完成;而编译器指令 @selector()

因此,关于 selector 的简要总结:

  • selector 返回的类型为 SEL;
  • SEL 是指向 objc_selector 结构体的指针;
  • objc_selector 虽然并没有公开结构体的实现,但其内部至少存在一个保存 selector 名字的字符串变量;
  • 关于 selector 的创建,若与共享缓存、NXMapTable映射表中的都未注册,则创建一个新的 SEL 并插入至 NXMapTable 中,同时保存于方法名的映射关系。

2. IMP

 IMP 表示指向方法实现地址的指针,当发起 Objective-C 消息后,最终要执行的代码就是由 IMP 指针来决定,SEL 的目的是为了查找方法最终实现的 IMP。若通过获取到实例对象指定方法的 IMP 并直接调用,则可以绕过消息传递流程,直接执行 IMP 对应的方法,这样可以提升访问效率。但也就意味着编译器并不会检查直接通过 IMP 去执行指定的方法,编译时期编译器并不能判断是否调用 IMP 错误,只有在运行时执行到 IMP 指向的方法实现时,才能判断是否正确。

关于 IMP 的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

 第一个参数传入一个指向 self 指针(指定类生成的实例对象的内存,或者类方法时指向元类的指针),第二个参数传入方法选择器,后续参数为可配置参数。

 调用 IMP 的方式在默认生成的项目工程下,调用编译器获取 IMP 会直接报错,项目配置中默认为下图配置:

 这样的话,IMP 被定义为无参数无返回类型的函数,关闭即可。还有更高效的方法,就是重新定义一个和有参数的 IMP 指针相同类型的指针,并把获取到 IMP 时将其强转为该类型。


3. Method

Method 结构体定义 typedef struct method_t *Method;,顺藤摸瓜去查看 method_t 的结构体内容。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

 结构体中,有关键作用的成员变量包含 SEL name; 方法名、const char *types; 返回类型的 encode 码以及 MethodListIMP imp; 方法地址的指针。

关于 Method 的存储位置,在runtime的那些事(二)——NSObject数据结构文章中已经有过说明,在编译时存放于 objc_class -> class_data_bits_t bits -> class_ro_t -> method_arrary_t *baseMethodList 中,而到了运行时 Method 会再存放于 objc_class -> class_data_bits_t bits -> class_rw_t -> method_arrary_t methods中。

 关于 Method 的初始化,是在 static Class realizeClass(Class cls) 函数中完成的,runtime的那些事(二)——NSObject数据结构也针对该函数做了源码层面的分析,这里不再进行说明。

在 Objective-C 语言中,允许我们通过 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 函数在运行时动态加载新的 Method 方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BOOL 
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);
}

static IMP 
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;
}

 在向 Class 添加 Method 时,判断要添加的 Method 是否已存在。若存在相同的 SEL 方法名,根据 BOOL 类型变量 replace 判断,若为 NO,则从已有的 Method 中取出 IMP 并返回;若为 YES则会将新的 IMP 与 对应的 SEL 方法名进行映射绑定。当 Class 中不存在指定的 SEL 方法名,则会向 Class 结构体中 class_rw_t 下的 method_array_t *methods 列表中注册添加新的 Method ,添加完成后当前 Class 类的内存地址发生变化,必须清除 Class 类以及子类的 bucket 缓存。


 此篇文章,先对 selector、IMP、Method 的概念做一次整理,下一篇文章会尝试从 runtime 源码上研究下消息传递的完整流程。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019.07.18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Golang流媒体实战之六:lal拉流服务源码阅读
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 《Golang流媒体实战》系列的链接 体验开源项目lal 回源 转推和录制 lalserver的启动源码阅读 Golang流媒体实战之五:lal推流服务源码阅读 Golang流媒体实战之六:lal拉流服务源码阅读 本篇概览 本文是《Golang流媒体实战》系列的第六篇,经过前面两篇的源码阅读后,咱们逐渐进入深入学习的状态,本篇继续阅读关键代码:拉流服务 为了
程序员欣宸
2023/04/12
5150
Golang流媒体实战之六:lal拉流服务源码阅读
Golang流媒体实战之七:hls拉流服务源码阅读
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是《Golang流媒体实战》系列的第七篇,继续学习一个重要且通用的知识点:hls拉流 在《体验开源项目lal》一文中,咱们先是用rtmp协议推流,然后就行了拉流操作,尽管只用rtmp推流,然而拉流的时候却可以使用多种协议:rtmp、flv、hls,这就有意思了,想必lal在处理推流数据时有特殊处理吧,所以才能用各种协议来拉流 为了弄明白其中原因,本
程序员欣宸
2023/04/24
1.4K0
Golang流媒体实战之七:hls拉流服务源码阅读
Golang流媒体实战之一:体验开源项目lal
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于《Golang流媒体实战》 因为工作需要,开始了流媒体开发学习,于是打算选择一个Go版本的开源流媒体服务器作为学习方向 lal是个不错的开源项目:项目活跃、功能齐全、有详细的中文资料,因此,就选择了它 仓库地址:https://github.com/q191201771/lal 文档地址:https://pengrl.com/lal/#/ 《Golang
程序员欣宸
2023/03/26
1.8K0
Golang流媒体实战之一:体验开源项目lal
golang源码阅读:livego直播系统
在分析源码之前,先搭建一个直播系统: 直播服务器 https://github.com/gwuhaolin/livego 播放站点 https://github.com/Bilibili/flv.js/ 推流 https://github.com/obsproject/obs-studio 首先启动直播服务器 ./livego --flv_dir=./data --level=debug 1,在启动livego服务后默认会监听以下端口: 8090端口:用于控制台,通过HTTP请求可查看与控制直播房间的推拉
golangLeetcode
2022/08/03
3.2K1
golang源码阅读:livego直播系统
Golang流媒体实战之四:lalserver的启动源码阅读
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 《Golang流媒体实战》系列的链接 体验开源项目lal 回源 转推和录制 本篇概览 要想深入了解lalserver,除了前面几篇文章的使用,还必须读源码,这里就从最基本的启动开始,再逐步延伸到深入了解各核心功能 本次源码阅读对应的lal代码仓库在这里:https://github.com/q191201771/lal 分支用的是master,截止目前的comm
程序员欣宸
2023/04/03
7350
SRS学习笔记(1)-推拉流代码阅读
SRS是一个用C++开发的开源流媒体集群服务, 能够提供直播点播的功能. github链接: https://github.com/ossrs/srs, 官方架构图如下(3.0版本):
EndevChen
2020/05/24
2.1K0
SRS学习笔记(1)-推拉流代码阅读
玩转直播系列之RTMP协议和源码解析(2)
实时消息传输协议(Real-Time Messaging Protocol)是目前直播的主要协议,是Adobe公司为Flash播放器和服务器之间提供音视频数据传输服务而设计的应用层私有协议。RTMP协议是目前各大云厂商直线直播业务所公用的基本直播推拉流协议,随着国内直播行业的发展和5G时代的到来,对RTMP协议有基本的了解,也是我们程序员必须要掌握的基本技能。
2020labs小助手
2021/05/17
1.9K0
KubeEdge云边隧道Stream源码解析
Stream是KubeEdge中提供云边隧道的模块,目前支持ApiServer向Kubelet发起的containerLog、exec和metrics请求。云边隧道基于WebSocket建造,支持双向传输和流式传输。
CNCF
2021/05/07
1.5K0
KubeEdge云边隧道Stream源码解析
RTMP协议详解及Wiresahrk抓包分析
本文主要讲解 RTMP 协议,并通过 wireshark 对 RTMP 进行抓包并分析。
Gnep@97
2023/11/08
6.1K0
RTMP协议详解及Wiresahrk抓包分析
golang kafka客户端实现
最近在弄golang框架的事情,连接kafka,目前采用的是sarama进行连接,开发测试是ok的,但是考虑到在生产环境中使用。sarama还是有些问题的,问题出在它的consumer上,不能够直接使用,需要进行简单的处理,首先是处理topic和groupid的问题。
Java架构师必看
2021/05/14
2.8K0
【开源品鉴】FRP源码阅读
frp 是一款高性能的反向代理应用,专注于内网穿透。它支持多种协议,包括 TCP、UDP、HTTP、HTTPS 等,并且具备 P2P 通信功能。使用 frp,您可以安全、便捷地将内网服务暴露到公网,通过拥有公网 IP 的节点进行中转,具体场景就是:将客户端部署到你的内网中,然后该客户端与你内网服务网络可达,当客户端与在公网的服务端连接后,我们就可以通过访问服务端的指定端口,去访问到内网服务。
于顾而言SASE
2025/07/04
1260
【开源品鉴】FRP源码阅读
2023-03-09:用golang调用ffmpeg,将流媒体数据(以RTMP为例)保存成本地文件(以flv为例)。
2023-03-09:用golang调用ffmpeg,将流媒体数据(以RTMP为例)保存成本地文件(以flv为例)。
福大大架构师每日一题
2023/03/09
1.3K0
2023-03-09:用golang调用ffmpeg,将流媒体数据(以RTMP为例)保存成本地文件(以flv为例)。
流媒体协议RTMP介绍
RTMP(Real Time Messaging Protocol)实时消息传送协议是Adobe公司为Flash播放器和服务器之间音频、视频和数据传输开发的开放协议。RTMP工作在TCP之上,默认使用端口1935。
liuzhen007
2021/01/31
2.6K0
流媒体协议RTMP介绍
FFmpeg流媒体处理-收流与推流
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10623968.html
叶余
2019/04/19
10.6K1
FFmpeg流媒体处理-收流与推流
Golang流媒体实战之三:转推和录制
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是《Golang流媒体实战》系列的第三篇,咱们不忙着写代码,继续熟悉开源流媒体服务器lal,今天练习的是流传输过程中两个重要功能:转推和录制 关于转推功能,可以从下图了解,FFmpeg推流到转推节点后,转推节点会将该路流推送到录制和源站两个节点,录制节点负责录制flv文件,源站作为媒体源,接受外部拉流(源站->拉流->VLC这个链路已在前文
程序员欣宸
2023/03/27
1.4K0
Golang流媒体实战之三:转推和录制
【Golang】快速复习指南QuickReview(九)——socket
Socket网路编程对于B/S项目来说,几乎不会涉及;但是如果涉及游戏服务器开发,或者上位机服务器开发,自定义通信协议,Socket网络编程就变得常见了。
DDGarfield
2022/06/23
3020
Kubeedge 代码解析(更新中)
beehive是一个基于go channel的消息框架,用于KubeEdge模块之间的通信。
operator开发工程师
2024/03/22
3030
Kubeedge 代码解析(更新中)
谈谈名字服务Polaris的sidecar模式
源码地址:https://github.com/polarismesh/polaris-sidecar
tunsuy
2023/08/19
2640
谈谈名字服务Polaris的sidecar模式
btcd p2p 网络分析
比特币依赖于对等网络来实现信息的共享与传输,网络中的每个节点即可以是客户端也可以是服务端,本篇文章基于比特币go版本btcd探索比特币对等网络的实现原理,整个实现从底层到上层可以分为地址,连接,节点三层,每层都有自己的功能与职责。下面逐一的分析这三个部分的构成与功能
魂祭心
2019/03/12
1.7K0
2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。
2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。
福大大架构师每日一题
2023/03/05
6100
2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。
推荐阅读
相关推荐
Golang流媒体实战之六:lal拉流服务源码阅读
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验