objc_msgSend汇编源码分析

引言

Objective-C是通过消息机制调用方法的,编译器会把所有消息发送转为objc_msgSend方法调用。说到objc_msgSend的汇编实现,大多数人会觉的是因为性能高才用汇编实现,几乎没有文章说其它原因。Objective-C所有方法都会转为objc_msgSend方法调用,然而每个方法参数和返回值都可能不一样,参数和返回值要怎么处理?

  • 本文首先会结合Objective-C Runtime机制深入分析objc_msgSend汇编实现。
  • 本文最后会从Calling Conventions角度分析objc_msgSend实现,利用Calling Conventions和汇编还可以实现很多黑科技。

Objective-C对象结构

Objective-C中消息发送核心数据结构如下:

//以下代码均为arm64平台
typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_object {
    isa_t isa;
}

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;  //class_rw_t*
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

union isa_t 
{
    Class cls;
    uintptr_t bits;
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
}

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

struct bucket_t {
    cache_key_t _key;//实际上是selector
    IMP _imp; //实际上是函数指针
}

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
}

NSObject子类的实例都有个isa指针,isa指向Class,Class有superclass、cache、实例方法、属性、protocol等Runtime信息,调用实例方法的时候就是通过isa指针找到Class,然后找到IMP调用实际的方法。
Class本身也是一个对象,也有isa指针,指向meta-class,meta-class也是一个对象,有类方法等属性,调用类方法的时候,就是通过Class对象的isa指针找到meta-class,然后找到IMP调用实际的方法。
实例、Class、meta-class关系如下图,图片来源

对象关系图

消息机制

当编译器遇到一个方法调用时,它会将方法的调用翻译成以下函数中的一个,
objc_msgSend、 objc_msgSend_stret、 objc_msgSendSuper 和 objc_msgSendSuper_stret。

  • 发送给对象的父类的消息会使用 objc_msgSendSuper ;
  • 有数据结构作为返回值的方法会使用 objc_msgSendSuper_stret 或 objc_msgSend_stret ;
  • 其它的消息都是使用 objc_msgSend 发送的。

objc_msgSend查找selector的IMP,然后调用实际的方法,主要包括以下流程:

  1. 查看cache是否有selector的IMP,如果有的话直接调用
  2. 如果没cache,最终会调用lookUpImpOrForward,从类方法列表查找IMP并缓存到cache
  3. 如果方法列表没有则会查找基类的方法,直到最上层基类(查找基类的时候也是先查找缓存,再查找方法列表)
  4. 如果基类也没查找到,则返回_objc_msgForward的IMP,走消息转发流程。

我们也可以自己通过class_getMethodImplementation拿到方法IMP(IMP是实际方法的函数指针),然后调用:

//[view addSubview:view2]
void (*funtion_pointer)(id, SEL, UIView*) = (void(*)(id, SEL, UIView*)) class_getMethodImplementation((id)view, @selector(addSubview:));
funtion_pointer(view, @selector(addSubview:), view2)

汇编源码

objc_msgSend汇编源码在Messengers.subproj目录,具体汇编如下:

objc_msgSend汇编源码
_objc_msgSend_uncached汇编源码

objc_msgSend汇编代码不长,结合objc源码比较容易看懂。需要注意的是isa和TaggedPointer格式,isa指针不是纯粹的指针,还保存很多其它信息,具体可以参考isa_t union定义,其中只有3到35位才是class指针,所以查找之前会通过mask转换成class指针。

isa格式

iOS系统为了提高性能和减小内存,使用了TaggedPointer来表示NSNumber、NSIndexPath等对象,对象并没有分配内存空间,而是把对象值保存在指针里面,只有指针无法容纳对象才会分配实际内存。TaggedPointer具体格式如下图,tag index表示具体Class,系统有维护一个全局映射表来保存tag index和Class的关系,具体可以查看objc_tag_index_t定义,查找到具体Class之后就跟正常oc对象一样查找IMP了。

TaggedPointer指针格式

Calling Conventions

arm64架构是通过q0-q7和x0-x7来传函数参数,可以看到objc_msgSend没对这几个寄存器做任何操作,找到IMP后直接通过br x17调用IMP,br告诉cpu不是子程序调用。
Objective-C所以方法发送都是通过objc_msgSend,每个方法返回值和参数都不一样,如果objc_msgSend像普通函数一样处理参数,为了处理不同参数类型和参数个数,可以使用varargs ,Objective-C调用的地方必须包裹成varargs,这样处理非常不灵活,objc_msgSend用了个很巧妙的技巧,就是对参数不做任何处理,查找到IMP后直接调用,因为在objc_msgSend开始执行时,栈帧(stack frame)的状态、数据,和各个寄存器的组合形式、数据,跟调用具体的函数指针(IMP)时所需的状态、数据,是完全一致的,所以我们用xcode调试的时候函数栈是看不到objc_msgSend,看上去就是消息发送过程完全没发生过,跟调用普通的c方法一摸一样。

黑科技

objc_msgSend用很巧妙的技巧处理参数问题,利用这种技巧可以做很多方法,比如可以实现Aspects的效果,在调用实际方法前做些hook操作,hook完后再调实际方法。也可以使用libffi处理参数问题,可以搞很多事情。

引用

Why objc_msgSend Must be Written in Assembly
面向切面 Aspects 源码阅读
What is a meta-class in Objective-C?
Objective-C 中的消息与消息转发

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,690评论 0 9
  • 关于OC中的消息发送的实现,在去年也看过一次,当时有点不太理解,但是今年再看却很容易理解。 我想这跟知识体系的构建...
    咖啡绿茶1991阅读 940评论 0 1
  • 消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 ...
    lylaut阅读 1,827评论 2 3
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 729评论 0 2
  • 不知道怎么开始写东西,想说的东西太多,但是没有说的对象。觉得自己一直过得好奇怪,明明不喜欢笑,总要笑得很开心。不喜...
    rainover阅读 601评论 2 0