Object-C Runtime 运行时
Runtime 运行时
Objective-C是动态语言,所以需要编译器的同时也需要一个运行时系统来动态创建对象、传递消息。在你需要的时候还能对其进行扩展,解决问题。Objective-C的Runtime是一个运行时的库(Runtime Library),主要使用C和汇编写的库,为C添加了面向对象的能力并创造了Objective-C。这个运行时系统就像一个操作系统一样:它让所有的工作正常运行。
有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)
Runtime有两个版本:Modern
和Legacy
。Object—C2.0采用的是Modern
版本的Runtime,只能在iOS和OS X10.5之后的64位程序上运行。32位程序采用Legacy
版本的Runtime。它们的区别在于更改实例变量时,Legary
需要重新编译其子类,而Modern`则不需要。
-
封装,在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,这些结构体和函数被runtime封装后,可以创建检查修改类对象和他们的方法。
-
找出方法的最终执行代码:当程序执行object doSomething时,会向消息接受者发送一条消息,runtime会根据消息接受者是否能响应该消息而做出不同的反应。
-
在编译时你写的Objective-C 函数调用的语法都会被翻译成一个C的函数调用,例如我们发送一个消息
[receiver message]
会被编译器转化为[objc_msgSend(receiver,selector)]
如果有参数的话则为[objc_msgSend(receiver,selector,arg1,arg2)]
,如果消息的接受者能够找到对应的方法,就执行此方法,如果没有,要么消息被转发,或是动态添加此消息,以上都没有的话程序就会crash。 -
从Object-C是动态语言理解的话就是,
[receiver message]
在编译的时候只是知道我将要向receiver发送一个message但是还没发送甚至不知道是否有这个消息以及这个消息有没有具体实现,只有在程序 运行时才回去查找实现,所以说Runtime对于Object-C来说非常重要,但其实通过终端我们是可以看到转换后的样子的。
打开终端进入工程目录
找到工程的.main文件层级 输入命令 clang -rewrite-objc main.m
文件夹内就可以找到.cpp文件,打开可以看到很多C的代码所以有人说OC是假的面向对象
- isMemberOfClass 只判断是否属于当前类的 实例 至于父类是什么关系它不管
- isKindOfClass 则能判断继承关系
- respondsToSelector 判断实例是否有这样方法
- instancesRespondToSelector 判断类是否有这个方法不能用在类的对象
Class为指向类的结构体指针
typedef struct objc_class *Class;
,该类的结构体包含:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // isa指针
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
isa 指针指向objc_class结构体的指针
- 每一个类的实例对象都有一个isa指针指向它的类,而每个类也有个isa指针指向它的元类 [metaClass] 元类里保存了类方法的列表。
NSObject *objc = [[NSObject alloc]init];
objc->isa; // 警告:会提示你用 **object_getClass(objc);**替换
- 当实例对象调用实例方法时,isa指针会到该实例方法的类
Subclass(class)
中去查询是否有此实例方法,如果没有则会去它的父类Superclass(class)
中去查找,一层一层向上查找。 - 当调用类方法时,isa指针会去该类元类中去查找
Subclass(meta)
如果没有则会去到元类的父类中去查找Superclass(meta)
- 万物皆对象,元类也不例外,元类也有它的isa指针,元类的isa指针只指向元类的根 根元类Root Class(meta) 同理根源类也是对象,它的isa指针指向 自己本身 因为根元类已经是最顶层的根类就像NSObject或NSProxy,它的父类则指向nil。
上面所述的三条总结起来就如下图:
SunnyXX之前的题
为什么[NSObject foo]可以调用实例方法?
Method 方法的类型
typedef struct objc_method *Method;
objc_method结构体如下
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
-
method_name
很显然是方法名 类型是SEL稍后解释。 -
method_types
char类型的指针用来存储参数类型和返回值类型。 -
method_imp
IMP类型 是个函数指针 稍后解释。
SEL
等同于Object-C中selector
,selector
是方法选择器typedef struct objc_selector *SEL;
SEL是指向objc_selector的指针,不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器 因为不同类的实例对象用相同的方法选择器时,会在各自的消息选择、实现地址、方法链表中根据 selector 去查找具体的方法实现IMP。这也是动态的过程,也就是说在运行之前甚至编译的时候我们都不知道最终会执行哪行代码。
IMP
IMP在objc中的定义为`typedef id (*IMP)(id, SEL, ...);
前面也提到它是个 函数指针上面SEL里提到只有在编译的时候才会知道最终实现哪行代码,就是因为编译时IMP这个函数指针指向了最终实现的代码。方法名相同的时候通过IMP的参数类型就能将它们区分开找到特定的那个方法的实现。
Cache
-
[receiver message]
当对象接收message时isa指针会去对应的对象中去查找该message,就相当于遍历MethodList,但是只有message这一个方法是我们所需要的,每次接收message时就要遍历一遍MethodList,这样太低效。所以引入了cache,将我们使用过的方法放到缓存中下次调用时先去cache中查找如果没有再去MethodList中遍历。 -
objc_cache结构体指针:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
南峰子的技术博客对objc_cache结构体描述的很详细:
- mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
- occupied:一个整数,指定实际占用的缓存bucket的总数。
- buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长
Ivar 实例变量的类型
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 名称
char *ivar_type OBJC2_UNAVAILABLE; // 类型
int ivar_offset OBJC2_UNAVAILABLE; // 偏移量
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
id是一个objc_object类型指针
typedef struct objc_object *id;
引入头文件#import <objc/runtime.h>
#import <objc/message.h>
`#import <objc/runtime.h>`
`#import <objc/message.h>`
Class cls = [per class];// 获取自己的类
const char *className = class_getName(cls); // 获取类名
NSLog(@"%s",className);
// 2. meta-class 元类
// 1>当我们调用实例方法时,系统会去类的列表里找对应名字的方法
// 2>当我们调用类方法时,系统会去meta-class(元类)的列表中找对应名字的方法
// 寻找对象的元类 object_getClass(填入的是类对象)
Class metaClass = object_getClass(cls);
BOOL isMetaClass = class_isMetaClass(metaClass);// 判断是不是元类
NSLog(@"是否是元类%d",isMetaClass);
// 3. 获取属性列表 如果有属性count就有值
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(cls,&count);
// 循环遍历类获取其属性
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSLog(@"Person类的成员变量列表下标%d以及属性名%s",i,ivar_getName(ivar)); // _name _sex _age _hobby
}
// 4.获取属性列表
unsigned int outCount = 0;
objc_property_t *properList = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properList[i];
unsigned int attributeCount = 0;
objc_property_attribute_t *attribute = property_copyAttributeList(property,&attributeCount); // 属性列表内部
for (int j = 0 ; j < attributeCount; j++) {
NSLog(@"%s : %s",attribute[j].name,attribute[j].value);
}
NSLog(@"Person类的属性列表下标%d以及属性名%s",i,property_getName(property));
}
// 方法列表
unsigned int methCount= 0;
Method *methList = class_copyMethodList(cls,&methCount);
for (int i = 0; i < methCount; i++) {
Method meth = methList[i];
NSLog(@"Person方法名%@",NSStringFromSelector(method_getName(meth)));
}
推荐阅读
-
Object-C Runtime 运行时
-
【转】java虚拟机运行时的内存分类以及出现异常分析 博客分类: JVM java虚拟机 jvm 异常
-
Java程序员面试题之一 博客分类: 面试题 面试题面向对象封装类异常运行时异常
-
我认识的所有runtime exception ArithmeticException
-
OpenCV计算机视觉编程记录(06)---------减色方法进行测试,比较其运行时间。
-
学些Object-c的意外收获 博客分类: ios cjava
-
关于Object-C 底层实现self isa 的理解 iOS
-
关于Object-C 底层实现self isa 的理解 iOS
-
学些Object-c的意外收获 博客分类: ios cjava
-
初识Runtime之KVO实现原理