iOS浅谈Objective-C方法调用的实质

引言

OC是一门动态运行时语言,其方法调用其实就跟C++或者Java或其他面向对象语言中的方法调用差不多,只是形式有些不一样而已。而OC得方法调用的术语为消息,下文我以消息来称方法调用。下面将详细介绍为什么OC是动态的,以及编译器在不为人知的背后做了什么事情。

在深入了解消息之前,必须要先了解三个必备的概念:1.Class 2.SEL 3.IMP,它们分别在objc/objc.h中的定义:

typedef struct objc_class *Class;

typedef struct objc_object {

     Class isa;

} *id;

typedef struct objc_selector  *SEL;

typedef id (*IMP)(id, SEL, ...);


Class的含义:

Class是一个指向 objc_class 的结构体指针,这个结构体标识每一个类的类结构。而 objc_class在 objc/objc_class.h 中定义如下:

struct objc_class {

    struct objc_class * isa;

    struct objc_class *super_class;  /*父类*/

    const char *name;/*类名字*/

    long version;/*版本信息*/

    long info;/*类信息*/

    long instance_size;/*实例大小*/

    struct objc_ivar_list *ivars;/*实例参数链表*/

    struct objc_method_list **methodLists;/*方法链表*/

    struct objc_cache *cache;/*方法缓存*/

    struct objc_protocol_list *protocols;/*协议链表*/

};

所以 Class 是指向类的结构体的指针,这个类的结构体含有一个指向父类类结构体的指针,该类方法的链表,该类方法的缓存以及其他必要信息。

每一个类实例对象的第一个实例变量是一个指向该对象的类结构的指针,叫做 isa。通过 isa,对象可以访问它对应的类以及对应类的父类,如图所示:


我们知道 id 是一个指向 objc_object 结构体的指针,它是一个不确定类型,该结构体只有一个成员就是 isa ,所以任何继承 NSObject 的类的对象都可以用 id 来指代,因为 NSObject 的第一个成员实例就是 isa 。


方法的含义:

这一所说的方法链表存储的是 Method 类型,Method 在头文件 objc_class.h 中定义如下:

typedef struct objc_method *Method;

typedef struct objc_ method {

    SEL method_name;      //方法的名称

    char *method_types;   //参数类型

    IMP method_imp;        //具体实现的函数指针

};

一个方法 Method 包含了方法选表 SEL(表示方法的名称),一个 types(表示方法的参数类型),一个 IMP(指向该方法的具体实现的函数指针)。


SEL的含义:

在引言中我们看到了SEL(方法选标)的定义:

typedef struct objc_selector  *SEL;

它是一个指向 objc_selector 指针,表示方法的名字。不同的类可以拥有相同的 selector,因为不同类的对象 performSelector 相同的 selector 时,会在各自的 selector (消息选标) 和 address (实现地址) 去查找 IMP (方法的实现),然后这个IMP执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们并不知道最终执行哪一些代码,只有在运行的时候,通过selector 去查询方法名,才能确定具体执行什么代码以及代码的实现。


IMP的含义:

在引言中我们看到了IMP的定义为:

typedef id (*IMP)(id, SEL, ...);

IMP是一个函数指针,这个被指向的函数包含了一个接收消息的对象 id(self 指针),调用方法的选标 SEL(方法名),以及不定个数的方法参数,并返回一个 id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码。


方法调用过程:

我这里设计一个 Person 类的对象去调用方法eat:

Person *person = [[Person alloc] init];

[person eat];

对 eat 的调用,编译器会通过运行时动态插入一些代码,将其转化为对方法具体实现 IMP 的调用,而这个 IMP 是通过在 Person 的类结构中的方法链表中找到 eat 的方法选标 SEL 对应的具体方法实现。

上面提及编译器插入的一些代码是什么代码?下面展开这个话题。


消息函数obj_msgSend:

编译器会将方法转换为消息函数objc_msgSend的调用,这个函数主要有两个参数:1.消息接受者id 和 2.消息对应的方法选标 SEL,这两个参数是隐藏的无需提供给开发者看到。此外同时会接收到消息中的任意参数,这些参数是开发者对方法设计时所用到的参数:

id objc_msgSend(id theReceiver, SELtheSelector, ...)

如果我声明一个Person类,并调用eat方法  [Person eat];  会被转化为如下形式的函数:

objc_msgSend(Person, @selector(eat));

消息函数 objc_msgSend 做了动态绑定所需要的一切工作:

1.消息函数首先找到 SEL(方法选标)对应的 IMP(方法实现);

2.然后将消息接收者对象以及方法中指定的参数传递给 IMP(方法实现);

3.最后将方法实现的返回值作为该函数的返回值来返回。

上述提到编译器方法调用的时候回自动插入的代码是该消息函数 objc_msgSend 的代码,我们不需要再代码中显示这个消息函数。当消息函数 objc_msgSend 找到对应的 IMP (方法实现)时,它直接调用这个方法的实现,并将消息中的所有参数都传递给方法实现,同时会传递两个隐藏的参数(1.消息接受者id 、 2.消息对应的方法选标 SEL)。这些参数帮主方法实现获得消息表达式的信息。在这两个隐藏参数中,self 更有用一些。实际上,self 是在方法实现中访问消息接收者对象的实例变量的途径。 


查找 IMP 的过程:

前面说了,objc_msgSend 会根据方法选标 SEL 在类结构的方法列表中查找方法实现 IMP。这里面有文章,我们在前面的类结构中也看到有一个叫 objc_cache *cache 的成员,这个缓存为提高效率而存在的。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。

在查找IMP时:

1.首先去该类的方法 cache 中查找,如果找到了就返回它;

2.如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将它加入cache中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销。

3.如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class 指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的 IMP,返回它,并加入 cache中;

4.如果在自身以及所有父类的方法列表中都没有找到对应的IMP,则看是不是可以进行动态方法决议;

5.如果动态方法决议没能解决问题,进入消息转发。

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

推荐阅读更多精彩内容