iOS编程基础-OC(八)-运行时系统的结构(续)
来源:互联网 发布:谷边由美 程序员 编辑:程序博客网 时间:2024/06/05 11:57
该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
第八章 运行时系统的结构
8.1.2 运行时系统库
苹果公司提供的OC运行时系统库实现了OC的面向对象特性和动态属性;
多数情况下,运行时系统库是在后台起作用,但也拥有公共的API;
我们可以在代码中直接使用它们;
这些API是用C语言编写的,含有一系列函数、数据类型和语言常量;
之前已经介绍过一些关键的系统数据类型,如objc_object、objc_class,和一些函数;
运行时系统库数据类型分为下列几类:
1)类定义数据结构(类、方法、实例变量、分类、IMP和SEL等);
2)实例数据类型(id、objc_object和objc_super);
3)值(BOOL);
函数分为下列几类:
1)对象消息;
2)类函数;
3)实例函数;
4)协议函数;
5)方法函数;
6)属性函数;
7)选择器函数;
此外,运行时系统库还定义了几个布尔型常量(YES、NO)和空值(NULL、nil和Nil);
运行时系统库的公共API是在头文件runtime.h中声明的;(可查看相关苹果开放资源-开源文档https://opensource.apple.com )
接下来我们来简单实践下;
1.使用运行时系统库API创建类
为了集中说明,我们新建一个类来进行实践:C8DynamicClass
#import <Foundation/Foundation.h>@interface C8DynamicClass : NSObject-(void)dynamicClassCreate;@end
//#import <Foundation/Foundation.h>#import <objc/message.h>#import <objc/runtime.h>#import "C8DynamicClass.h"@implementation C8DynamicClassNSString * greeting(id self, SEL _cmd){ return @"Hello world";}-(void)dynamicClassCreate{ //以动态方式创建一个类DynamicClass Class dynamicClass = objc_allocateClassPair([NSObject class], "DynamicClass", 0); //以动态方式添加一个方法,使用已有的方法(description)获取特征 Method description = class_getInstanceMethod([NSObject class], @selector(description)); const char * types = method_getTypeEncoding(description); class_addMethod(dynamicClass, @selector(greeting), (IMP)greeting, types); //注册这个类 objc_registerClassPair(dynamicClass); //使用该类创建一个实例并向其中发送一条消息 id dynaObj = [[dynamicClass alloc] init]; NSLog(@"%@",objc_msgSend(dynaObj,NSSelectorFromString(@"greeting")));//Build Setting--> Apple LLVM 6.0 - Preprocessing--> Enable Strict Checking of objc_msgSend Calls 改为 NO }
之后运行如下代码:
C8DynamicClass * dyna = [[C8DynamicClass alloc]init]; [dyna dynamicClassCreate];
log:
2017-12-12 14:11:50.513944+0800 精通Objective-C[8950:739960] Hello world
我们看到,动态创建的类dynamicClass被正确使用了,通过消息传递成功调用了相应的方法;
我们来具体看下:
必须使用#import <objc/message.h>这条语句,才能将运行时系统库中的消息传递API(如objc_msgSend())包含到文件中;
创建新类的逻辑:
创建了一个类对(类及其元类:元类的理解很重要,稍后会介绍);
向这个类添加了一个指向greeting()函数的方法;
之后,在运行时注册这个类;
这样就能创建这个类的实例;
方法签名是通过使用拥有相同签名的方法获取的(NSObject的description方法);
最后,创建一个类实例并向其发送一条消息;
2.实现运行时系统的对象消息传递
运行时系统库含有可用于访问下列信息的函数(括号中为函数名):
1)对象的类(objc_getClass)
2)类的父类(class_getSuperclass)
3)对象的元类(objc_getMetaClass)
4)类的名称(objc_getName)
5)类的版本信息(class_getVersion)
6)以字节为单位的类尺寸(class_getInstanceSize)
7)类的实例变量列表(class_copyIvarList)
8)类的方法列表(class_copyMethodList)
9)类的协议列表(class_copyProtocolList)
10)类的属性列表(class_copyProperyList)
运行时系统库数据类型和函数为运行时系统库提供了实现各种OC特性(如对象消息传递)所需的数据类型和函数;
运行时系统中的消息传递操作:
我们先来熟悉一个结构;
(Pic8_0)
当程序向对象发送消息时;
运行时系统会通过自定义代码中的类方法缓存和虚函数表,查找类的实例方法;
为了找到该方法,运行时系统会搜索整个类层次结构,找到后,就会执行该方法的实现代码;
运行时系统库含有很多实现对象消息传递的设计机制;
我们从虚函数表开始,介绍几个这类机制;
1)通过虚函数表查找方法:
运行时系统库定义了一种方法数据类型(objc_method):
struct objc_method{
SEL method_name;
char * method_types;
IMP method_imp;
};
typedef objc_method Method;
我们来仔细看下这个结构:
method_name描述方法的名称,类型为SEL;
method_types变量描述了方法参数的数据类型;
method_imp是一个类型为IMP的变量,它在方法被调用时提供方法的地址(C语言函数至少会拥有两个参数,self和_cmd);
因为在执行程序的过程中调用方法的操作可能会执行很多次,所以运行时系统需要使用一种快速高效的方法查询和调用机制;
虚函数表也称为 分派表,是编程语言中常用的动态绑定支持机制;
OC运行时系统库实现了一种自定义的虚函数表分派机制,专门用于最大限度地提高性能和灵活性;
虚函数表:
是一个用于存储IMP类型数据的数组;(IMP类型指向OC方法的实现代码);
每个运行时系统类实例(objc_class)都有一个指向虚函数表的指针;
每个类还拥有最近调用过方法的指针缓存;
这样就优化了方法调用操作的性能;
我们来看一下执行方法查询操作的运行时系统逻辑:(∨∧)
(Pic8_1)
如果通过虚方法表寻找IMP指针,并且找到了,会将这个IMP存储在缓存中备用;
这种设计使运行时系统能够执行快速高效的方法查询操作;
2)通过dyld共享缓存使选择器拥有唯一性;
dyld是什么?
dyld是一种服务,用于定位和加载动态库;
它含有共享缓存,能够使多个进程共用相应的动态库;
dyld共享缓存:
共享缓存中含有一个选择器表,从而使运行时系统能够通过该缓存访问共享库和自定义类的选择器;
运行时系统就是通过dyld共享缓存 获取共用库的选择器;
dyld共享缓存 与 选择器拥有唯一性的关系?
启动OC程序的额外开销与确保选择器唯一性时间成正比;
在一个可执行程序中选择器名称必须有唯一性,但实际开发中很多选择器都是重名的;
所以,运行时系统需要为每个选择器名称选择一个规范的SEL指针;然后为每次调用和各个方法更新元数据,以便获取唯一值;
这个过程必须在启动应用程序时执行并且会消耗系统资源(内存和程序启动时间);
为了提高该过程的效率,运行时系统才通过dyld共享缓存使选择器拥有唯一性;
(把所有的选择器都加到一个表里,不重复就唯一了)
3)消息分派:
在使用方法缓存和虚函数表获取IMP指针之后,运行时系统就会使用自己优化的自定义汇编语言代码实现的分派方法;
这种代码有时也被称为蹦床,作用就是找到正确的代码并且跳过去;
我们知道objc_msgSend()函数一定会寻找与消息指定的接收器和选择器对应的IMP指针,然后跳转到该指针指向的地址执行此处存储的代码;
蹦床代码实现的就是优化之后的这一过程;
为什么要使用蹦床代码(这是一种消息分派方法)?
这是因为在OC程序中,通过消息执行的调用操作无处不在,仅在启动时,运行时系统库函数objc_msgSend()(作用是执行消息传递)就会调用数百万次;
因此,一点点的优化都会带来很大的改善;
4)访问类实例方法:
前面介绍了运行时系统查找和调用实例方法的方式,再来看看运行时系统库处理类方法的方式(即对类方法执行对象消息操作);
实际上OC中的类也是对象,因此他们也能接收消息;
如:N[SObject alloc];
这条语句展示了向类发送消息的方式;
那问题来了,运行时系统如何找到调用方法的呢?
运行时系统通过元类实现这个功能;
元类:(metaclass)
是一种特殊的对象,运行时系统使用其中含有的信息能够找到并调用类方法;
在OC中,每一个类都拥有一个独一无二的元类(因为每个类有可能拥有一个独一无二的类方法列表);
运行时系统库提供了访问元类的函数;
可以像常规类一样使用元类,使用元类可以查看类的层次结构;
正如某各类可以通过父类指针指向它的父类一样,元类也可以通过父类指针指向其对应父类的元类;
参考(Pic8_0);
基类的元类会使用其父类指针指向该基类本身;
在这种继承层次结构中,所有实例、类和元类都是从基类继承而来的;
对象的isa变量会指向描述该对象的类;
因此可以使用该变量访问这个对象的实例方法、实例变量等;
在OC中,类(对象)的isa变量会指向描述类(及其类方法等)的元类;
综上,运行时系统通过下列方式对实例和类方法执行对象消息传递操作;
1)当源代码向对象发送消息时,运行时系统会通过相应的类实例方法虚函数表,获得合适的实例方法实现代码并跳转执行该方法;
2)当源代码向类发送消息时,运行时系统会通过该类的元类类方法虚函数表,获得合适的类方法实现代码并跳转执行该方法;
讲了很多内容了,希望大家注意理解,接下来通过一个例子梳理下这些概念;
3.检查类方法
为弄清楚运行时系统数据结构使用元类的方式,以及元类在整个层次结构中的作用;我们通过一段代码理解下;
我们需要使用上一节定义的类C8TestClass1:
@interface C8TestClass1:NSObject{ @public int myInt; } @end @implementation C8TestClass1 @end
测试代码如下:
id metaClass = objc_getMetaClass("C8TestClass1"); long mclzSize = class_getInstanceSize([metaClass class]); NSData * mclzData = [NSData dataWithBytes:(__bridge const void *)metaClass length:mclzSize]; NSLog(@"C8TestClass1 metaClass contains %@",mclzData); class_isMetaClass(metaClass)?NSLog(@"Class %s is a metaclass",class_getName([metaClass class])):NSLog(@"Class %s is not a metaclass",class_getName([metaClass class]));
log:(我们把上一节的代码一起运行,log如下)
2017-12-13 11:07:17.884517+0800 精通Objective-C[14709:1595535] C8TestClass1 object tc1 contains <a0dc1909 01000000 5a5a5a00 00000000>
2017-12-13 11:07:17.884712+0800 精通Objective-C[14709:1595535] C8TestClass1 object tc2 contains <a0dc1909 01000000 c3c3c300 00000000>
2017-12-13 11:07:17.884808+0800 精通Objective-C[14709:1595535] C8TestClass1 memory address = 0x10919dca0
2017-12-13 11:07:17.884940+0800 精通Objective-C[14709:1595535] C8TestClass1 class contains <78dc1909 01000000 a8ae150a 01000000>
2017-12-13 11:07:17.885035+0800 精通Objective-C[14709:1595535] C8TestClass1 superclass memory address = 0x10a15aea8
2017-12-13 11:07:17.885141+0800 精通Objective-C[14709:1595535] C8TestClass2 class contains <c8dc1909 01000000 a8ae150a 01000000>
2017-12-13 11:07:17.885232+0800 精通Objective-C[14709:1595535] C8TestClass2 superclass memory address = 0x10a15aea8
2017-12-13 11:07:17.885364+0800 精通Objective-C[14709:1595535] Hello world
2017-12-13 11:07:17.885493+0800 精通Objective-C[14709:1595535] C8TestClass1 metaClass contains <58ae150a 01000000 58ae150a 01000000 c02f1100 80600000 07000000 01000000 c0ef0700 80600000>
2017-12-13 11:07:17.885581+0800 精通Objective-C[14709:1595535] Class C8TestClass1 is a metaclass
分析:
首先,objc_getMetaClass(),获取已命名类的元类定义;
之后,显示了与其关联的数据类型;
最后,使用了class_isMetaClass()测试对象是否为元类;
这里使用__bridge是因为:
[NSData dataWithBytes:(__bridge const void *)metaClass length:mclzSize];方法的第一个参数接收的是类型为(const void *)的变量;
ARC是禁止从OC对象到(void *)类型的直接转换;因此通过桥接转换(第六章),可以使编译器处理这些情况;
因为不存在所有权转换(赋予变量metaClass的对象仍旧由ARC管理),所以使用了__bridge标记;
再来关注下log:
元类中有isa指针(58ae150a 01000000),父类指针(58ae150a 01000000)和附加信息(其他的部分);
父类是NSObject;
元类的isa指针都是执行基类的,这里C8TestClass1继承自NSObject,所以我们会发现isa指针和父类指针的值是相同的,都指向了NSObject类;
基类的super指针为NULL;(这句是网上查的)
这些内容都是对运行时系统API及其实现方式的补充介绍,接下来介绍用于直接与运行时系统交互的OC API;
8.2 与运行时系统交互
OC程序通过运行时系统交互实现动态特性;
这些交互分为三个等级:
1)OB源代码;
2)Foundation框架中的NSObject类的方法;
3)运行时系统库API;
—————— OC源代码 ——————
框架与服务 运行时系统API
—————— 编译器 ——————
—————— 运行时系统库 ——————
前面几节介绍了编译器和运行时系统库的作用,下面介绍Foundation框架中的NSObject类的运行时特性;
8.2.1 NSObject类的运行时方法
OC语言提供了许多动态编程功能:
运行时系统提供了一系列API,使用它们可以直接与运行时系统进行交互;
但这些API使用C语言编写的,因此要求使用过程式编程方法;
作为一种替代解决方案,Foundation框架中的NSObject类提供了一系列方法,使用这些方法可以实现许多与运行时系统API相同的功能;
继承自NSObject类的类都可以直接使用这些方法;
NSObject类的运行时方法提供的功能:
1)对象内省;
2)消息转发;
3)动态方法决议;
4)动态加载;
8.2.2 执行对象内省
我们定义一个需要的类C8Greeter;
@interface C8Greeter:NSObject@property (readwrite , strong) NSString * salutation;-(NSString *)greeting:(NSString *)recipient;@end@implementation C8Greeter-(NSString *)greeting:(NSString *)recipient{ return [NSString stringWithFormat:@"%@,%@",[self salutation],recipient];}@end
编写测试代码如下:
C8Greeter * greeter = [[C8Greeter alloc] init]; [greeter setSalutation:@"Hello"]; if ([greeter respondsToSelector:@selector(greeting:)] && [greeter conformsToProtocol:@protocol(NSObject)]) { //使用NSObject的运行时方法performSelector:withObject:向实例发送一条消息; id result = [greeter performSelector:@selector(greeting:) withObject:@"Monster!"]; NSLog(@"%@",result); }
log:
2017-12-13 13:50:36.721951+0800 精通Objective-C[15990:2010671] Hello,Monster!
这段代码中,使用了NSObject类的运行时方法执行了对象内省;
满足条件时,还会使用NSObject的运行时方法performSelector:withObject:向实例发送一条消息;
8.3 小结
本章详细介绍了运行时系统结构中的关键组成部分;
到目前为止,我们至少应该理解了运行时系统实现面向对象特性和动态功能的方式;
本章要点:
1)OC运行时有两部分构成:编译器和运行时系统库;
编译器会接受OC源代码并生成由运行时系统库执行的代码;
运行时系统库会与所有OC程序链接(在链接阶段);
这两个一起实现了OC语言面向对象特性和动态功能;
2)运行时系统库API定义了一系列数据类型、函数和常量;
这些数据类型和函数中的多数都与OC语言元素(对象 类 协议 方法 和实例变量等)一一对应;
运行时系统库的公用API是使用C语言编写的;
3)运行时系统库实现了各种机制以增强程序的性能和可扩展性;
典型的有方法缓存、虚函数表和dyld共享缓存;
4)运行时系统库使用元类查找和调用类方法;
元类是一种特殊的数据类型,运行时系统使用其中的信息可以查找和调用方法;
5)Foundation框架中的NSObject类提供了一系列方法,使用可以调用运行时功能和行为;
这些方法可以执行对象内省、动态方法决议、动态加载和消息转发操作;
本章内容至此就结束了,内容比较多,仔细理解,多有助益。
- iOS编程基础-OC(八)-运行时系统的结构(续)
- iOS编程基础-OC(八)-运行时系统的结构
- iOS编程基础-OC(七)-运行时系统
- iOS编程基础-OC(七)-运行时系统(续)
- iOS编程基础-OC(九)-专家级技巧:使用运行时系统API(续)
- iOS编程基础-OC(九)-专家级技巧:使用运行时系统API
- iOS编程基础-OC(四)-内存管理(续)
- iOS编程基础-OC(十一)-Foundation框架中的系统服务:网络、应用及文件系统服务
- iOS编程基础-OC(十一)-Foundation框架中的系统服务:并发机制和线程
- OC基础回顾(八)内存管理
- OC -基础(八) 学习中。。。
- iOS编程基础-OC(三)-对象和消息传递
- iOS编程基础-OC(四)-内存管理
- iOS编程基础-OC(五)-预处理器
- iOS编程基础-OC(六)-专家级技巧:使用ARC
- OC 运行时系统
- Linux系统基础(八)
- 黑马程序员-OC笔记(八) Foundation 结构体 NSString
- 笔记17.12.11
- js 表达式和运算符
- java静态方法重写举例
- jdbc链接oraclde ora-12505 错误解决
- 板子上电的一些指令(简单)
- iOS编程基础-OC(八)-运行时系统的结构(续)
- 解决点击事件穿透的问题
- 金额输入框正则表达式,金额校验
- mysql基本概念
- 解决File "temp.py", line 10 SyntaxError: Non-ASCII character '\xe5' in file temp.py on line 10, but no
- 移动社交游戏开发的要点是什么?
- hive中UDTF编写和使用(转)
- 设计模式知识连载(11)---继承_7:多继承
- 十进制格式化 DecimalFormat的用法