Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
iOS_selector、SEL、IMP、Method都是什么,以及之间的关系
​ 在 Objective-C中使用发送消息的形式来调用方法,其中涉及到 Runtime库中定义的 SEL、 IMP、 Method,它们分别表示什么,以及它们之间的关系。
mikimo
2022/07/20
9170
runtime的那些事(一)——runtime基础介绍
一、 什么是runtime? 二、 runtime 版本 三、 与 runtime 的三种交互方式 四、 消息机制的基本原理与执行流程 五、 动态解析与消息转发
我只不过是出来写写代码
2019/04/22
1.7K0
runtime的那些事(一)——runtime基础介绍
iOS runtime探究(二): 从runtime开始深入理解OC消息转发机制你要知道的runtime都在这里
你要知道的runtime都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向: 从runtime开始: 理解面向对象的类到面向过程的结构体 从runtime开始: 深入理解OC消息转发机制 从runtime开始: 理解OC的属性property 从runtime开始: 实践Category添加属性与黑魔法method sw
WWWWDotPNG
2018/04/10
1K0
iOS runtime探究(二): 从runtime开始深入理解OC消息转发机制你要知道的runtime都在这里
iOS开发·runtime原理与实践: 基本知识篇(类,超类,元类,super_class,isa,对象,方法,SEL,IMP)
Tips:苹果公开的源代码在这里可以查,https://opensource.apple.com/tarballs/
陈满iOS
2018/09/10
1.9K1
iOS开发·runtime原理与实践: 基本知识篇(类,超类,元类,super_class,isa,对象,方法,SEL,IMP)
iOS底层原理总结 - 探寻Runtime本质(三)
方法调用的本质 本文我们探寻方法调用的本质,首先通过一段代码,将方法调用代码转为c++代码查看方法调用的本质是什么样的。 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m [person test]; // --------- c++底层代码 ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test")); 通过上述源码可以看出c++底层代码
xx_Cc
2018/07/03
5650
iOS运行时Runtime基础
本文主要整理了Runtime的相关知识。对于一个iOS开发者来说,掌握Runtime的重要性早已不言而喻。OC能够作为一门优秀的动态特性语言,在其背后默默工作着的就是Runtime。在网上也看过很多资
梧雨北辰
2018/07/11
9760
iOS 小技能:Method Swizzling (交换方法的IMP)
利用Objective-C Runtimee的动态绑定特性,将一个方法的实现与另一个方法的实现进行交换。交换两个方法的实现一般写在分类的load方法里面,因为load方法会在程序运行前加载一次,而initialize方法会在类或者子类在 第一次使用的时候调用,当有分类的时候会调用多次。
公众号iOS逆向
2022/08/22
1.9K0
iOS 小技能:Method Swizzling (交换方法的IMP)
深入浅出 Runtime(二):数据结构
Objective-C的面向对象都是基于C/C++的数据结构——结构体实现的。 我们平时使用的所有对象都是id类型,id类型对象对应到runtime中,就是objc_object结构体。
师大小海腾
2020/04/16
8710
iOS底层原理总结 - 探寻Runtime本质(二)
Class的结构 通过上一章中对isa本质结构有了新的认识,今天来回顾Class的结构,重新认识Class内部结构。 首先来看一下Class的内部结构代码,对探寻Class的本质做简单回顾。 struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bit
xx_Cc
2018/07/04
1.2K0
oc runtime
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
conanma
2021/05/11
6440
【IOS开发高级系列】Objective-c Runtime专题总结
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
江中散人_Jun
2023/10/16
4720
【IOS开发高级系列】Objective-c Runtime专题总结
深入浅出 Runtime(三):消息机制
在前面的文章说过,Runtime 是一个用C、汇编编写的运行时库。 在底层汇编里面如果需要调用 C 函数的话,苹果会为其加一个下划线_, 所以查看objc_msgSend函数的实现,需要搜索_objc_msgSend(objc-msg-arm64.s(objc4))。
师大小海腾
2020/04/16
8320
关于runtime
Runtime是一套C语言的API,基本是用 C 和汇编写的,封装了很多动态性相关的函数,在这里下到苹果维护的开源代码。主要是使用官方Api,解决我们框架性的需求。
honey缘木鱼
2019/12/26
6550
关于runtime
runtime如何通过selector找到对应的IMP地址?
类对象中有类方法和实例方法的列表,列表中记录着方法的名词、参数和实现,而selector本质就是方法名称,runtime通过这个方法名称就可以在列表中找到该方法对应的实现。
赵哥窟
2018/09/13
1.8K0
深入浅出 Runtime(一):初识
对于 NSString *string = [[NSMutableArray alloc]init];
师大小海腾
2020/04/16
1.1K0
Objc Runtime 总结
Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。Runtime是C和汇编编写的,这里http://www.opensource.apple.com/source/objc4/可以下到苹果维护的开源代码,GNU也有一个开源的runtime版本,他们都努力的保持一致。苹果官方的Runtime编程指南
用户7451029
2020/06/16
8660
iOS底层原理总结 - 探寻Runtime本质(四)
首先来看一道面试题。 下列代码中Person继承自NSObject,Student继承自Person,写出下列代码输出内容。
xx_Cc
2018/08/02
9710
iOS底层原理总结 - 探寻Runtime本质(四)
iOS开发-Runtime详解
iOS开发-Runtime详解 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。比如: [receiver message]; // 底层运行时会被编译器转化为: objc_msgSend(receiver, selector) // 如果其还有参数比如: [receiver message:(id)arg...]; // 底层运行时会被编译器转化为: objc_msgSend(receiver, selec
用户1941540
2018/05/11
8180
iOS Runtime Method IMP指针详解
OC是消息转发机制,代码在编译的时候会生产Runtime中间代码,运行的时候执行Runtime代码,我们也可以动态的添加Runtime代码。
星宇大前端
2019/01/15
1.6K0
神奇的Runtime
[receiver message]不是一个简单地方法调用,而是在编译阶段被编译器转化为
Helloted
2022/06/06
7310
神奇的Runtime
相关推荐
iOS_selector、SEL、IMP、Method都是什么,以及之间的关系
更多 >
交个朋友
加入架构与运维学习入门群
系统架构设计入门 运维体系构建指南
加入架构与运维工作实战群
高并发系统设计 运维自动化实践
加入前端学习入门群
前端基础系统教学 经验分享避坑指南
换一批
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档