趣谈iOS运行时的方法调用原理
一个成熟的计算机语言必然有丰富的体系,复杂的容错机制,处理逻辑以及判断逻辑。但这些复杂的逻辑都是围绕一个主线丰富和展开的,所以在学习计算机语言的时候,先掌握核心,然后了解其原理,明白程序语言设计的实质和当时选择这种处理方式的原因是极其必要的,而且也是学习语言的捷径。
所以在学习的过程中,需要把握几个核心
先专注主线,后丰富周边;
先宏观了解,后微观精通;
多设身处地思考,理解代码设计的原因;
理解代码设计的原理和优化。
OC中处理方法的业务逻辑和其他语言不同,OC语言是动态语言(动态绑定,动态加载(dynamatic binding),动态类型)。其中动态加载就涉及到OC的运行时。在OC中,方法是动态实现的,调用方法实际就是在发送消息。
试想一下,一个方法的实现必然包含三个部分:
1.执行方法的对象
2.方法名称
3.不确定的参数
SEL只是一个方法名称IMP才是执行方法最终的函数,IMP包含这三个部分IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象 id(self 指针),调用方法的选标 SEL(方法名),以及不定个数的方法参数,并返回一个 id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。
提问时间到了:
动态和静态有什么区别?
执行方法是怎么实现的?
OC的方法和C语言的函数原理一样么?
动态和静态有区别的。首先我们从最表层理解,一个方法的实现必然要包含执行者,方法名和不确定的参数和返回值。无论是静态或者动态方法都必须这三个必要元素(动态和静态的区别就在于在何时确定这些必要元素)。
方法的执行包含编译和运行两个过程。
静态方法是在编译时已经确定了三个要素,且不能更改。若类型不对,就会直接发出警告。
而OC的动态方法可以直接跳过编译,在运行时才开始添加函数调用,决定执行方法的三个要素。这就是动态方法(至于怎么执行,下面开始讲解)
这三个元素是如何确定的呢?首先我们看一段示例代码:
Dog *aDog = [[Dog alloc]init];
[aDog run];
在执行方法时,是怎么确定的呢?
此时我们需要注意OC内部方法的实质:OC中,方法实现实质就是发送消息。
[aDog run];代码的实质就是[ objc_sendMsg],它会找到执行方法的三个要素,找到就按照规则执行。
发送消息是通过 objc_send(id, SEL, ...) 来实现的,它首先会在对象的类对象的 cache,methodlist 以及父类对象的 cache,methodlist 中依次查找 SEL 对应 的 IMP,如果没有找到且实现了动态方法决议机制就会进行决议。
如果没有实现动态方法决议机制或决议失败且实现了消息转发机制就会进入消息转发流程,否则程序 crash。也就是说如果同时提供了动态方法决议和消息转发,那么动态方法决议先于消息转发,只有当动态方法决议依然无法正确决议 selector 的实现,才会尝试进行消息转发。当然,实际过程不可能那么简单,在开发语言之初,肯定会完善各种复杂场景和做了很多优化,接下来我们一起研究下OC对方法执行和扩展和优化:
第一步:先找方法
第二步:动态方法决议
第三部:消息转发
最后: 报错
消息转发
通常,给一个对象发送它不能处理的消息会得到出错提示,然而,Objective-C运行时系统在抛出错误之前,会给消息接收对象发送一条特别的消息 forwardInvocation 来通该对象,该消息的唯一参数是个 NSInvocation 类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation,方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对 象来处理,而不抛出错误。
1.首先去该类的方法cache中查找,如果找到了就返回它;
2.如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将 它加入 cache 中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销;
3.如果在该类的方法列表中没找到对应的IMP,在通过该类结构中的super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中;
4.如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议(后面有专文讲述这个话题);
5.如果动态方法决议没能解决问题,进入下面要讲的消息转发流程。便利函数:我们可以通过 NSObject 的一些方法获取运行时信息或动态执行一些消息。
class 返回对象的类:
isKindOfClass,isMemberOfClass 检查对象是否在指定的类继承体系中;
respondsToSelector 检查对象能否相应指定的消息;
conformsToProtocol 检查对象是否实现了指定协议类的方法;
methodForSelector 返回指定方法实现的地址;
performSelector:withObject 执行 SEL 所指代的方法。
OC做为一门面向对象语言,自然具有面向对象的语言特性,如封装、继承、多态。他具有静态语言的特性(如C++),又有动态语言的效率(动态绑定、动态加载等)。整体来说,确实是一门不错的编程语言。
OC的动态语言特性
现在,让我来想想OC的动态语言特性。OC的动态特性表现为了三个方面:动态类型、动态绑定、动态加载。
之所以叫做动态,是因为必须到运行时(runtime)才会做一些事情。
(1)动态类型
动态类型,说简单点就是id类型。动态类型是跟静态类型相对的。像内置的明确的基本类型都属于静态类型(int、NSString等)。静态类型在编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(runtime),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。
(2)动态绑定
动态绑定(dynamic binding)貌似比较难记忆,但事实上很简单,只需记住关键词@selector/SEL即可。先来看看“函数”,对于其他一些静态语言,比如c++,一般在编译的时候就已经将将要调用的函数的函数签名都告诉编译器了。静态的,不能改变。而在OC中,其实是没有函数的概念的,我们叫“消息机制”,所谓的函数调用就是给对象发送一条消息。这时,动态绑定的特性就来了。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调用什么方法,需要传什么参数进去。这就是动态绑定,要实现他就必须用SEL变量绑定一个方法。最终形成的这个SEL变量就代表一个方法的引用。这里要注意一点:SEL并不是C里面的函数指针,虽然很像,但真心不是函数指针。SEL变量只是一个整数,他是该方法的ID。以前的函数调用,是根据函数名,也就是字符串去查找函数体。但现在,我们是根据一个ID整数来查找方法,整数的查找字自然要比字符串的查找快得多!所以,动态绑定的特定不仅方便,而且效率更高。
(3)动态加载
动态加载就是根据需求动态地加载资源。我对动态加载比较陌生,所以就没什么可总结的啦。等以后慢慢完善。
趣谈iOS运行时的方法调用原理的更多相关文章
- iOS运行时 -- Runtime(摘抄自网络)
运行时(iOS) 一.什么是运行时(Runtime)? 运行时是苹果提供的纯C语言的开发库(运行时是一种非常牛逼.开发中经常用到的底层技术) 二.运行时的作用? 能获得某个类的所有成员变量 能获得某个 ...
- JAVA的List接口的remove重载方法调用原理
前言 说真的,平常看源码都是自己看完自己懂,很少有写出来的冲动. 但是在写算法的时候,经常用到java中各种集合,其中也比较常用到remove方法. remove有重载函数,分别传入参数是索引inde ...
- iOS运行时使用(动态添加方法)
1 举例 我们实现一个Person类 然后Person 其实是没得对象方法eat:的 下面调用person的eat方法 程序是会奔溃的 那么需要借助运行时动态的添加方法 Person *p = [[ ...
- iOS 运行时使用(交换两个方法)
举例 在创建了如下代码 NSString *str=nil; NSURL *url =[NSURL URLWithString:str]; NSLog(@"%@",url); 但是 ...
- iOS运行时编程(Runtime Programming)和Java的反射机制对比
运行时进行编程,类似Java的反射.运行时编程和Java反射的对比如下: 1.相同点 都可以实现的功能:获取类信息.属性设置获取.类的动态加载(NSClassFromString(@“clas ...
- Objective-C运行时编程 - 方法混写 Method Swizzling
摘要: 本文描述方法混写对实例.类.父类.不存在的方法等情况处理,属于Objective-C(oc)运行时(runtime)编程范围. 编程环境:Xcode 6.1.1, Yosemite,iOS 8 ...
- iOS-浅谈runtime运行时机制-runtime简单使用(转)
转自http://www.cnblogs.com/guoxiao/p/3583432.html 由于OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtim ...
- iOS运行时工具-cycript
cycript是大神saurik开发的一个非常强大的工具,可以让开发者在命令行下和应用交互,在运行时查看和修改应用.它确实可以帮助你破解一些应用,但我觉得这个工具主要还是用来学习其他应用的设计(主要是 ...
- OC运行时和方法机制笔记
在OC当中,属性是对字段的一种特殊封装手段. 在编译期,编译器会将对字段的访问替换为内存偏移量,实质是一种硬编码. 如果增加一个字段,那么对象的内存排布就会改变,需要重新编译才行. OC的做法是,把实 ...
随机推荐
- C 字符串倒转,XCode中编译
正在学习ios开发,在前期学习c时,常规方法直接倒转数组的值,只能用于非中文字符,否则出现乱码, 在网上找了点资料,可能是 IDE不一致,一直得不到自己想要的值.花时间自己改了一下,正常通过 //字符 ...
- 对Gearman中client,worker,jobserver的理解
在gearman的官网http://gearman.org/有以下的一段说明 A Gearman powered application consists of three parts: a clie ...
- Codeforces 712C Memory and De-Evolution
Description Memory is now interested in the de-evolution of objects, specifically triangles. He star ...
- 一步一步理解 Java 企业级应用的可扩展性
摘要:本文主要介绍如何理解 Java 应用的扩展方式以及不同类型的扩展技术和具体技巧,介绍一些有关 Java 企业级应用的一般扩展策略. 老实说,"可扩展性"是个全面且详尽的话题, ...
- Java 去除HTML标签转化成纯文本
package com.ahgw.common.global; import java.util.regex.Pattern; /** * 截取HTML代码 * * @author YangJunpi ...
- C语言嵌入式系统编程修炼之二:软件架构篇
模块划分的"划"是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求.C语言作为一种结构化的程序设计语言,在模块的划分上主要依据功能(依功能进行 ...
- Spring MVC 中的REST支持
本部分提供了支持 RESTful web 服务的主要 Spring 功能(或注释)的概述. @Controller 使用 @Controller 注释对将成为 MVC 中控制器的类进行注释并处理 HT ...
- ECHO.js 纯javascript轻量级延迟加载
演示 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf ...
- Linux Kernel ‘perf’ Utility 本地提权漏洞
漏洞名称: Linux Kernel ‘perf’ Utility 本地提权漏洞 CNNVD编号: CNNVD-201309-050 发布时间: 2013-09-09 更新时间: 2013-09-09 ...
- HashMap循环遍历方式及其性能对比
主要介绍HashMap的四种循环遍历方式,各种方式的性能测试对比,根据HashMap的源码实现分析性能结果,总结结论. 1. Map的四种遍历方式 下面只是简单介绍各种遍历示例(以HashMap为 ...