文章来自小笨狼的iOS博客,一直认为csdn的博客UI不太好看,看博客不太爽。所以自己搭建了一个博客。

欢迎各位去链接中看我的博客。也欢迎大家加QQ群讨论iOS技术问题

经过两个多月的面试,工作最终尘埃落定了。这两个多月的面试过程中,我发现非常多底层基础的东西大公司非常看重。比方:RunLoop,RunTime,Block等。

这篇文章主要是介绍RunTime中函数调用的机制,知识点有一定深度。也是面试过程中能力的体现。

1.Objective-C中的函数调用

对于C语言,函数调用是由编译器直接转化完毕的,在编译时程序就開始查找要运行的函数(C语言函数调用原理)。而在OC中,我们将函数调用称为消息发送。在编译时程序不查找要运行的函数。必须等到真正运行时,程序才查找要运行的函数。

样例:在C语言中。仅申明一个函数。不去实现。

其它地方调用此函数。编译时就会报错(C语言编译时查找要执行的函数,找不到所以报错)。

而相同的情况在OC中并不会报错,仅仅有在执行时候才会报错。(OC执行时才查找要执行的函数)

2.Objective-C函数调用的底层实现

Objective-C之所以能做到执行时才查找要执行的函数主要归功于runTime的SDK。以下我们来看看Objective-C是怎么让程序拥有执行时特性的。

在runTime的SDK下有一个objc_msgSend()的方法

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

当我们写下一行代码[obj doSth];,在编译时,编译器会将我们的代码转化为

    objc_msgSend(obj,@selector(doSth));

objc_msgSend()方法实现了函数查找和匹配,以下是它的原理:

  1. 依据对象obj找到对象类中存储的函数列表methodLists
  2. 再依据SEL@selector(doSth)methodLists中查找相应的函数指针method_imp
  3. 依据函数指针method_imp调用响应的函数。

3.objc_msgSend的实现细节

前面我们仅仅是简单的介绍了objc_msgSend()的原理,以下我们来具体看看objc_msgSend是怎么实现的。

首先对于随意一个NSObject对象以下都有一个isa的属性,指向对象相应的Class

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

依据对象调用就可以拿到相应的Class

以下我们来看看Class

typedef struct objc_class *Class;

struct objc_class {
Class isa; // 指向metaclass Class superclass; // 指向父类Class
const char *name; // 类名
uint32_t version; // 类的版本号信息
uint32_t info; // 一些标识信息。标明是普通的Class还是metaclass
uint32_t instance_size; // 该类的实例变量大小(包含从父类继承下来的实例变量);
struct old_ivar_list *ivars; //类中成员变量的信息
struct old_method_list **methodLists; 类中方法列表
Cache cache; 查找方法的缓存。用于提升效率
struct old_protocol_list *protocols; // 存储该类遵守的协议
}

由上面的代码我们能看到Class是一个结构体指针,指向objc_class结构体。在objc_class中存放着methodLists方法列表。所以依据Class我们能够直接找到methodLists

以下我们来看看怎么从methodLists中找到相应的函数指针

struct old_method_list {
void *obsolete; //废弃的属性
int method_count; //方法的个数
/* variable length structure */
struct old_method method_list[1]; 方法的首地址
}; struct old_method {
SEL method_name; //方法相应的SEL
char *method_types; //方法的类型
IMP method_imp; //方法相应的函数指针
};

对于old_method_list结构体。他存储了old_method方法个数和方法首地址。我们能够把他当做一个可变长度的old_method数组。

開始我也不明确为什么是method_list[1],数组的大小怎么会是1呢?后来才想通因为数组的大小是不定的,不同的类相应的不同的方法个数。所以定义时仅仅存储首地址,在实际使用过程中再扩展长度

对于old_method结构体。他由SEL,type。IMP三个成员组成。由此可知,我们仅仅要在method_list中找到了old_method就可以拿到函数指针IMP。

以下是查找的代码:

static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
int i;
if (!mlist) return nil;
for (i = 0; i < mlist->method_count; i++) {
old_method *m = &mlist->method_list[i];
if (m->method_name == sel) {
return m;
}
}
return nil;
}
  1. 查找函数是个内联函数,传入old_method_listSEL。返回old_method
  2. 首先对old_method_list数组判空,假设为空,返回nil
  3. 遍历old_method_list数组。依据SEL匹配,找到old_method

4.函数调用的性能优化

上面部分我们已经讲完了函数调用的基本过程。

在看完上面部分之后大家可能会有疑惑:Objective-C的函数调用是如此的复杂。会不会导致执行起来很慢呢?毕竟每调用一个函数都要走这么多过程。

别急,事实上在调用的过程中苹果对其做了一些性能优化。使得其调用并不比C语言非常多。以下我们来详细看看做了哪些性能优化:

4.1 SEL的使用

大家可能早就有疑惑了,前面一直在说SELSEL究竟是个什么东西?

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

苹果对SEL的官方解释是:一种不透明的类型,它代表着一个方法选择器。

SEL本质事实上是一个int类型的地址。指向存储的方法名。对于每个类,都会分配一块特殊空空间。专门存储类中的方法名。SEL就是指向相应方法名的地址。因为方法名字符串是唯一的,所以SEL也是唯一的。

为什么不直接用法名而使用SEL呢?这个问题没有找到比較官方的资料。个人觉得因为方法名是字符串,SEL是int类型,使用时int类型更方便。效率更高(特别是比較相等时。字符串的比較比int的比較效率低非常多)

4.2 cache的使用

我们来细致分析一下函数的调用过程:

obj->isa->methodLists
old_method->method_imp
  1. 因为isaobj的成员变量,methodListsisa的成员变量。所以用obj能够直接拿到methodLists
  2. 因为method_impold_method的成员变量,所以用old_method能够直接拿到method_imp

所以函数调用过程的主要时间消耗在methodLists中查找old_method

cache就是用来优化这个查找过程的。

我们能够把cache简单当成一个哈希表。keySELValueold_method

由此可知,从cache中查找old_method相当简单高效。

methodLists中查找old_method的真正过程分为例如以下两步:

  1. 通过SELcache中查找old_method,若找到了直接返回,若未找到运行2
  2. methodLists中查找old_method,找到之后先将old_method插入cache中以方便下次查找,再返回old_method

由此可知,在第一次调用某个函数时。会比較慢,由于cache中没有这个函数。第二次调用时就会很快了

总结

这篇文章主要是讲Objective-C中函数调用的过程,其它不太相关的东西就被忽略了,比如objc_class结构体中其它成员变量作用,本类中查找不到函数怎么去父类中查找,假设没有找到函数怎么办?这些问题我可能兴许还会有文章介绍,如今临时先写到这吧。假设有人想要了解能够在评论区提问或者加QQ群讨论。大家有什么异议的地方也能够和我说

iOS开发RunTime之函数调用的更多相关文章

  1. iOS开发-Runtime详解

    iOS开发-Runtime详解 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的.比如: [recei ...

  2. iOS 开发-- Runtime 1小时入门教程

    1小时让你知道什么是Objective-C Runtime,并对它有一定的基本了解,可以在开发过程中运用自如. 三.Objective-C Runtime到底是什么东西? 简而言之,Objective ...

  3. iOS开发·runtime原理与实践: 消息转发篇(Message Forwarding) (消息机制,方法未实现+API不兼容奔溃,模拟多继承)...

    本文Demo传送门: MessageForwardingDemo 摘要:编程,只了解原理不行,必须实战才能知道应用场景.本系列尝试阐述runtime相关理论的同时介绍一些实战场景,而本文则是本系列的消 ...

  4. iOS开发-Runtime详解(简书)

    简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的.比如: [receiver message]; // ...

  5. ios开发runtime学习四:动态添加属性

    #import "ViewController.h" #import "Person.h" #import "NSObject+Property.h& ...

  6. ios开发runtime学习三:动态添加方法(实际应用少,面试)

    #import "ViewController.h" #import "Person.h" /* 1: Runtime(动态添加方法):OC都是懒加载机制,只要 ...

  7. ios开发runtime学习二:runtime交换方法

    #import "ViewController.h" /* Runtime(交换方法):主要想修改系统的方法实现 需求: 比如说有一个项目,已经开发了2年,忽然项目负责人添加一个功 ...

  8. iOS开发runtime学习:一:runtime简介与runtime的消息机制

    一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/messag ...

  9. iOS开发--Runtime的简单使用之关联对象

    一.Runtime关联对象的方法简介: 在<objc/runtime.h>中,有三个关联的方法,分别是: objc_setAssociatedObject objc_getAssociat ...

随机推荐

  1. 【Linux命令】mysql数据库常用操作命令

    #数据库操作: #建立数据库 create database data_name #删除数据库 drop database data_name #查看: show databases; #表操作: # ...

  2. NSNumber与NSInteger的区别

    Objective-C 支持的类型有两种:基本类型 和  类. 基本类型,如同C 语言中的 int 类型一样,拿来就可以直接用. 而类在使用时,必须先创建一个对象,再为对象分配空间,接着做初始化和赋值 ...

  3. boost库学习随记六:使用同步定时器、异步定时器、bind、成员函数回调处理、多线程的同步处理示例等

    一.使用同步定时器 这个示例程序通过展示如何在一个定时器执行一个阻塞等待. //makefile #-------------------------------------------------- ...

  4. 2015暑假acm短训小结

    时间很快,短训已经结束,短短20天,心里有一些思绪想要记下. 收获: 从最近发的随笔中可以看出,做得最多的是搜索——Dfs,Bfs.对于搜索,如何描述状态,如何压缩状态,如何决定下一个结点,是否可以剪 ...

  5. 3644 - X-Plosives(水题,并差集)

    3644 - X-Plosives A secret service developed a new kind of explosive that attain its volatile proper ...

  6. Yii Framework2.0开发教程(2)使用表单Form

    第一步.接着教程(1).我们在controllers/ZhyoulunController.php中加入两处, 1) use app\models\EntryForm; 和 2) public fun ...

  7. XMPP个人信息展示

    在现阶段的通信服务中.各种标准都有,因此会出现无法实现相互连通,而XMPP(Extensible Message and presence Protocol)协议的出现.实现了整个及时通信服务协议的互 ...

  8. Java 输出通过 InetAddress 获得的 IP 地址数组

    使用 InetAddress 获取 IP 地址会得到一个 byte 数组 如果你直接输出这个数组,你会发现 IP 地址中的某些位变成了负数 比如 61.135.169.105 会输出成 61.-121 ...

  9. Js基础操作

    var a="zhangsan"; document.write(a+":I love JavaScrip"); a="lisi"; doc ...

  10. activity变成Dialog的步骤

    1.在布局文件上最外层最好使用RelativeLayout来布局,如果使用LinearLayout来布局的话,显示对话框的话,感觉会有点问题: 要在预览中看到框框,并且是match_parent的,而 ...