《精通Objective-C》 运行时(Runtime) 阅读笔
前言
学习iOS开发已经几个月了,越学越觉得Objective-C的水不浅啊。有关这门语言,我还有很多知识点需要恶补。本来还在想要不要去学swift,感觉2016年swift会更火。现在觉得Objective-C都没学好,还想学swift。做梦吧。Swift虽然语法变得简单了,但是同时多了不少特性,相对Objective-C来说,深入学习的坑一点都不浅。
为了打好基础,从图书馆里借了《精通Objective-C》,此书主要讲述Objective-C的运行时环境、Foundation框架等底层知识,我直接跳读了有关运行时系统的3章——第7、8和9章。
个人阅读技术书的步骤是:
- 整体阅读完一章内容,并用铅笔做些笔记
- 把书里的代码示例亲自打一遍,并做好注释和小结
- 对不懂的内容多次阅读
- 做小结,记录关键知识点
这样做的原因是确保自己在忘记部分知识点的时候,通过自己的代码示例和笔记可以快速查漏补缺,不用重新拿起技术书。(ps:最重要的原因是书是从图书馆借的,下次可不一定能借到)
Demo
RuntimeLearningDemo 提取码: 95nu
第7章.运行时系统
问题1 运行时系统的运行方式
当源代码被编译时,编译器(运行时系统的组成部分)会创建数据结构好函数调用语句,使用它们以动态方式将接收器(类/对象)和消息选择器与方法的实现代码对应起来。在执行程序时,运行时系统库(运行时系统的另外一个组成部分)利用这些信息找到并调用适当的方法。
问题2 运行时系统实现对象消息传递的方式
编译器会将[接收器 消息]形式的对象消息,转换为声明中含有方法签名的(ANSI)C函数调用语句。因此,为了生成正确的对象消息传递代码,编译器需要获得选择器值和方法签名。选择器可以从对象消息表达式提取,而方法签名的匹配最好确保拥有不同特征的方法也拥有不同的名称。
问题3 使用动态类型的好处
- 使用动态类型可以简化类接口,无须为不同输入参数类型编写不同的方法声明。
- 动态类型还可以提供非常大的灵活性,在执行程序的过程中改进程序使用的数据类型,并在不重新编译和重新部署的情况下引入新的数据类型。
问题4 NSObject类的API中用于执行对象内省的方法
- isKindOfClass:判断某个对象是否是该类或其子类的实例
- respondsToSelector:判断某个对象是否会对选择器作出回应
- conformsToProtocol:判断某个对象是否遵守指定的协议
- methodSignatureForSelector:为选择器提取方法签名
本章要点
Objective-C运行时系统的特性和关键组件:
-
使用Objective-C中的消息传递(对象消息传递)特性可以调用类和对象中的方法。对象消息传递是一种动态特性。
对象消息传递 - 消息传递表达式包含接收器(接收器的对象/类)和消息,而消息又由选择器和相应的输入参数构成。
- 选择器是一种分段的文本字符串,每个字段以冒号结尾并且后跟参数。
- 选择器数据类型(SEL)是一种特殊的Objective-C数据类型,它用于在编译源代码时使用具有唯一性的标识符替换选择器。使用@selector关键字或Foundation框架中的NSSelectorFronString()函数,可以创建类型为SEL的变量。
- 方法签名(method signture)定义了方法的输入参数和返回值的数据类型。
- 使用动态类型(dynamic typing)功能可以在运行程序时决定对象的类型。Objective-C通过id数据类型支持动态类型。
-
动态绑定(dynamic binding)是指在程序运行时(而不是编译时),将消息与方法对应起来的处理过程。动态绑定功能实现了OOP的多态性,并且能够在不影响已有代码的情况下,将新对象和代码添加到程序中,从而降低了对象之间的耦合度。
动态绑定 - 使用动态方法决议能够以动态方式实现方法。使用@dynamic指令可以告知编译器与某个属性关联的方法能够以动态方式实现。NSObject类中含有resolveInstanceMethod:和resolveClassMethod:方法,使用它们能够以动态方式分别实现由选择器指定的实例和类方法。
- 使用动态加载功能可以根据需要加载Objective-C程序的可执行代码和源代码,而无须在启动应用程序时加载它的所有组件。
- Foundation框架中NSObject类的API含有非常多用于执行对象内省的方法。在运行程序时,这些API能够以动态方式查询方法的信息,还可以测试对象的继承性、行为和一致性。
第8章.运行时系统的结构
问题1 编译器如何为Objective-C类和对象生成可执行代码,以及它如何实现对象消息
- 当编译器解析对象消息(发送消息的表达式),如[接收器 消息],它会生成调用运行时系统库中函数objc_msgSend()的代码。每条消息都是以动态方式处理的。对于源代码中的类和对象来说,编译器创建了执行对象消息操作所需的数据结构。
- 当编译器解析含有类定义和对象的Objective-C源代码时,它会生成相应的运行时数据结构。
Objective-C中的类与运行时系统库中的Class数据类型对应。Class数据类型是指向objc_class标识符的不透明数据类型的指针。
如typedef struct objc_class *Class
使用运行时系统库中的函数可以访问Class(即objc_class)数据类型的变量。
编译器在解析Objective-C对象的源代码时,会生成创建运行时对象类型的可执行代码。
问题2 Objective-C对象和类的运行时数据结构
实际上,所有Objective-C对象和类的运行时类型都是以isa指针开头的。
-
与Objective-C对象对应的运行时数据结构——objc_object数据类型是一种带objc_object标识符的C语言结构,如下:
objc_object数据结构 -
与Objective-C中id数据类型对应的运行时数据类型也是一种C语言结构,该结构被定义为指向objc_object的指针,如下:
- Objective-C块对象也拥有相应的运行时数据结构
问题3 Objective-C运行时系统的数据类型和函数
*** 数据类型***
- 类定义数据结构(类、方法、实例变量、分类、IMP和SEL)
- 实例数据类型(id、objc_object和objc_super)
- 值(BOOL)
*** 函数***
- 对象消息
- 类函数
- 实例函数
- 协议函数
- 方法函数
- 属性函数
- 选择器函数
*** 布尔型常量(YES和NO)***
*** 空值(NULL、nil和Nil)***
问题4 Objective-C运行时系统的函数及其含义
函数 | 含义 |
---|---|
objc_getClass | 对象的类定义 |
class_getSuperClass | 类的父类 |
objc_getMetaClass | 对象的元类定义 |
class_getName | 类的名称 |
class_getVersion | 类的版本信息 |
class_getInstanceeSize | 以字节为单位的类尺寸 |
class_copyIvarList | 类的实例变量列表 |
class_copyMethodList | 类的方法列表 |
class_copyProtocolList | 类的协议列表 |
class_copyPropertyList | 类的属性列表 |
问题5 运行时系统中的消息传递操作
消息传递操作注意:
- 某个类通过父类指针指向它的父类,元类也通过其父类指针指向其对应类父类的元类
- 基类(NSObject类)的元类会使其父类指针指向该基类本身
- 对象的isa变量会指向描述该对象的类
- 类(对象)的isa变量会指向描述类(及其类方法等)的元类
总结
- 当源代码向对象发送消息时,运行时系统会通过相应的类实例方法虚函数表,获得合适的实例方法实现代码并跳转执行该方法
- 当源代码向类发送消息时,运行时系统会通过该类的元类类方法虚函数表,获得合适的类方法实现代码并跳转执行该方法
问题6 运行时系统库用于实现对象消息传递的设计机制
- 通过虚函数表查找方法
- 通过dyld共享缓存使选择器拥有唯一性
- 消息分派
- 访问类实例方法
问题7 运行时系统库中的方法数据类型
方法数据类型问题8 运行时系统的方法查询和调用机制——虚函数表
虚函数表是一个用于存储IMP类型(Objective-C方法的实现代码)数据的数组。
- 每个运行时系统类实例(objc_class)都有一个指向虚函数表的指针
- 每个类实例还拥有最近调用过方法的指针缓存
具体流程如下:
问题9 dyld
dyld是一种系统服务,用于定位和加载动态库。
- 含有共享缓存,能够使多个进程共用这些动态库
- dyld共享缓存中还含有一个选择器表,从而使运行时系统能够通过该缓存访问共享库和自定义的选择器。
因此,运行时系统能够通过dyld共享缓存获取共用库的选择器,而且需要做的仅是为应用中的自定义类更新选择器。
问题10 消息分派
objc_msgSend()函数一定会寻找与消息指定的接收器和选择器对应的IMP指针,然后跳转到该指针指向的地址执行此处的代码。完成该操作后,运行时系统就会使用已优化的自定义汇编语言代码分派方法。这段代码有时叫蹦床,作用是找到正确的代码并跳转过去。
问题11 访问类实例方法
- Objective-C中的类也是对象,也能接受消息,如
[NSObject alloc] - 运行时系统是通过元类实现向类发送消息的功能。
- 元类是一种特殊的类对象,运行时系统使用其中含有的信息能够找到并调用类方法。
问题12 与运行时系统交互
Objective-C程序通过与运行时系统交互实现动态特性。
与运行时系统交互
问题13 NSObject类的运行时方法
运行时系统API是使用C语言编写的,因此,Foundation框架中的NSObject类提供了一系列封装运行时系统API功能的方法,这些运行时方法所提供的功能:
- 对象内省
- 消息转发
- 动态方法决议
- 动态加载
本章要点
介绍运行时系统结构中的关键组成部分,深入理解运行时系统实现面向对象特性和动态功能的方式:
- Objective-C运行时系统由两个主要部分构成:编译器和运行时系统库。编译器会接受Objective-C源代码并生成由运行时系统库执行的代码。运行时系统库会与所有Objective-C程序链接(在链接阶段)。这两个关键组成部分一起实现了Objective-C语言的面向对象特性和动态功能。
- 运行时系统库API定义了一系列数据类型、函数和常量。运行时系统库的公用API是使用C语言编写的。
- 运行时系统库实现了各种特性和机制,使用它们可以增强应用程序的性能和可扩展性。典型的特性和机制包括方法缓存、虚函数表和dyld共享缓存。
- 运行时系统库使用元类查找和调用类方法。元类是一种特殊的数据类型,运行时系统使用其中的信息可以查找和调用类方法。
- Foundation框架中的NSObject类提供了一系列方法,使用它们可以调用运行时系统功能和行为。这些方法可以执行对象内省、动态方法决议、动态加载和消息转发操作。
后记
看完3章Runtime的内容,但是还是意味未尽。
-
选择谷歌搜索
Snip20160122_1.png -
或者,微博搜索
- 之后,按需选文。
粗略的翻看了搜索到的文章,感觉还是没有《精通Objective-C》讲的系统且全面,当然如果能耐心去看官方文档,那自然是最好的。网上的大多数文章要么是只是对Runtime某个特性大笔着墨,或者只是对知识点的查漏补缺。要想真正学习Objective-C的底层知识,还是得通过技术书系统的学,然后配合官方文档和WWDC视频补充。
Runtime的学习刚刚开始,剩下只有通过实践和阅读优秀的源码慢慢学习了。
参考文章
*** ibireme大神这两篇文章是通过苹果官方源代码注释并讲解的。 ***
Objective-C 中的类和对象 by ibireme
Objective-C 中的消息与消息转发 by ibireme
从AOP框架学习iOS Runtime by 林熠 from 阿里巴巴技术协会