iOS 从runtime理解消息发送

什么是runtime

runtime就是运行时,在实际开发中使用runtime的场景并不多,但是了解runtime有助于我们更好的理解OC的原理,从而提高开发水平。
我们都知道高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体,本文正是通过runtime源码分析来讲解runtime是如何将面向对象的类转变为面向过程的结构体。

消息发送

众所周知OC是一门动态语言,在编译期应用根本不知道什么对象该做什么事情,那我们的程序到底是怎么执行相关操作的呢?那我们就必须了解Runtime这个库了,Runtime把对象需要做的事情放到运行时期来处理。
消息发送主要就是objc_msgSend这个方法,任何方法的调用最终都会转换为objc_msgSend方法进行处理。

    id objc_msgSend(id self, SEL op, ...);

在上面的代码中我们可以看到objc_msgSend方法有3个参数,self :是消息的接受者 op : 是消息的方法名字 最后一个是参数列表。

[cell setListData:model];

cell是消息的执行者,setListData是选择子,model是参数。
选择子和参数组合起来就是消息。

typedef struct objc_class *Class;     //类,结构体指针,实际也是对象

/// Represents an instance of a class.
struct objc_object {    //实例对象的isa指针指向类对象,而类对象的isa指向元类
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;    //对象,结构体指针

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;     //isa指针

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;  // 父类
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;  // 类名
    long version                                             OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
    long info                                                OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
    long instance_size                                       OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;   // 方法定义的链表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;   // 缓存方法列表
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;  // 协议列表
#endif
} OBJC2_UNAVAILABLE;

在<objc/objc.h>的源码中我们可以发现,对象的本质就是一个结构体指针,它的isa指针指向类对象,类对象中包括方法列表,属性列表等等,每个对象都会有一个isa指针,类对象的isa指向元类,元类里存放类方法列表。

当发送消息给实例对象时,消息是在寻找这个对象的类的方法列表(实例方法)。
当发送消息给类对象时,消息是在寻找这个类的元类的方法列表(类方法)。


屏幕快照 2019-06-20 15.57.12.png

OC方法会被Runtime转换为objc_msgSend函数,然后根据接受者和消息来调用适当的方法。每个方法里都有2个属性,SEL是方法的编号,IMP是方法地址。runtime的黑魔法就是改变了IMP方法编号的指向。

消息传递流程

  • 对象会根据isa指针找到这个对象自己所属的类,用消息里的选择子名称在所属类的方法列表中依次遍历,如果找到相符合的方法名称,就会根据IMP指针跳转到方法的实现代码进而执行代码逻辑,如果找不到相符的名称,就会沿着继承体系向上查找,如果在根类还没有查找到该方法,此时就会执行消息转发的操作。
  • 每次的方法执行都会在底层运行很多的步骤,方法的查询是非常耗时间和性能的,因此苹果提供了一个缓存机制。当一个类的方法别调用后,系统就会将这个方法放入该类的方法类表中,方便下次调用大大缩短了查找时间。

方法缓存机制是用散列表来实现的,这样可以提高方法的查找速度。

散列表的原理

  • 初始时, 为对象的cach_t分配一个空间, 值为NULL。
  • 调用方法时, 为对象发送一个 SEL 消息, 如 @selector(est), 将这个方法缓存。
  • 系统用 SEL 与 _mask 作按位与计算: @selector(est) & _mask
  • 根据按位与得到的结果去检查对应索引的空间是否为NULL , 如果为NULL 就将这个bucket_t 缓存在索引2对应空间
  • 如果不为空, 索引减1, 再检查是否为NULL, 依次类推. 如果索引<0, 则使索引 =_mask - 1, 直至找到索引对应空间为NULL, 再缓存

_mask 是缓存结构体中的属性,是散列表的长度减1。

系统为什么要把方法编号与_mask进行与运算呢?

与运算符 &
只有对应的二个二进位都为1时,结果位才是1
10010 & 00010 = 00010

由此可以知道得到的数不会大于_mask,也就不会超出分配的空间,如果分配的空间不够,散列表会自动扩容,空间扩大2倍,并且散列表清空。

iOS方法查询是怎样实现的呢?

  • 向对象发送一个消息时,例如:@selector(eat)。
  • 系统会用eat方法里的SEL(方法编号)与_mask作与计算。
  • 把按位与得到的结果当做散列表的索引, 判断其中的 SEL 是否与传过来的 SEL 相同, 如果相同, 这个_imp就是寻找的方法。
  • 如果不相同, 索引减1, 再比较SEL, 依次类推. 如果索引<0, 则使索引 = _mask - 1, 直至找到_imp。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容