runtime理论知识

时间:2022-07-05 04:03:38

http://southpeak.github.io/2014/10/25/objective-c-runtime-1/

转载http://www.jianshu.com/p/6b905584f536

http://www.jianshu.com/p/b827c5000924

1.OC是一门动态语言,将很多静态语言在编译和链接时期做的事放到了运行时来处理。

动态语言的优势在于:写代码更具灵活性,可以把消息转发给我们想要的对象。或者随意交换一个方法等

所以OC不仅需要一个编译器,还需要一个运行时系统,即Objc Runtime,运行时系统就想操作系统一样,让所有工作正常运行

Objc Runtime其实是一个runtime库,用C和汇编写的,这个库是的C语言有了面向对象的能力.

2.runtime库主要做的事情

在这个runtime库中,对象可以用C语言的结构体表示.方法可以用C函数实现,再加一些额外的特性,这些结构体和函数被runtime封装之后

我们就可以在程序运行时创建检查修改类,对象和他们的方法了。

执行流程

【objc doSomething】-->向objc发送一条消息dosomething-->runtime根据对象处理做出反应

[self doSomethingWithVar:var1];-->objc_msgSend(self,@selector(doSomethingWithVar:),var1);

现在runtime的版本Modern runtime

类与对象的基础数据结构

首先根据NSObject类,

OC类是由Class类型来表示的
@interface NSObject <NSObject> {

    Class isa  OBJC_ISA_AVAILABILITY;

}
Class实际是一个指向
objc_class结构体的指针
typedef struct objc_class *Class;

查看objc/runtime.h中objc_class结构体的定义如下:

struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; #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:OC中所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)

~~ super_class: 指向该类的父类,如果该类已经在最顶层的根类如NSObject或者NSProxy,则super_class为NULL

~~cache:用于缓存最近使用的方法,一个接收者对象接受到一个消息时,他会根据isa指针去查找响应消息的对象。在实际应用中,这个对象的只有一部分方法是常用的,很多方法其实很少用的的上,

在这种情况下。如果每次来消息时,我们都去Method list中遍历一遍,性能势必会很差。这时chache就用上了,把方法缓存到cache列表中,下次调用runtime就会先去cache列表中查找,如果cache列表中没有

才去遍历Methodlist中查找方法,对于那些常用的方法,提高了调用的效率。

~~version:类的版本信息。对于对象的序列化非常有用,可以让我们识别不同类定义版本中实例变量的布局的改变。

举个栗子

NSArray *array = [[NSArray alloc] init];

~~先执行alloc,因为NSArray没有+alloc方法,于是去父类NSObject去查找

~~检查NSObject是否响应alloc方法,发现响应,于是监测NSArray类,根据其大小分配内存空间,然后把isa指针指向NSArray类,同时,+alloc被加进NSObject的cache列表里

~~接着执行init方法,如果NSArray响应init方法,直接加入NSArray的cache,如果不响应,则去父类查找

~~在后期的操作中,如果再以这种方式创建数组,则会直接从cache中取出响应的方法,直接调用。

objc_object和id(OC对象和id)

/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY; //指向其类的isa指针
}; /// A pointer to an instance of a class.
typedef struct objc_object *id;

//当创建一个特定类对象的时候,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据,NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。

//我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。

objc_cache

struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[] OBJC2_UNAVAILABLE;
};

~~ mask 一个整数,指定分配的缓存的bucket的总数。方法查找过程中,runtime根据这个字段确定开始线性查找数组的位置 ,指向方法selector的指针与该字段做一个AND位操作(index = (mask&selector))

~~ occupied 一个整数   指定实际占用的缓存bucket的总数。

~~buckets:指向Method数据结构指针的数组  这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长

元类(Meta Class)

所有的类自身也是一个对象,我们可以向对象发送消息,即调用(类方法)

加入一个类调用类方法时,比如NSArray *array = [NSArray array]; 实际是+array的消息发送给了NSArray对象,既然NSArray是对象,那么NSArray就是一上文中的objc_object

结构,objc_object包含一个isa指针,普通的对象的isa指针指向这个对象,比如这个array就指向NSArray,那NSArray的isa指针指向什么呢?指向NSArray的元类

demo

随便写一个类继承自NSObject,类名命名为MetaClassTest

 NSLog(@"objcet is %@  super object is %@", [MetaClassTest class], [MetaClassTest superclass]);
Class currentClass = [MetaClassTest class];//返回的是类本身,并不是元类
for (int i = ; i < ; i ++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
BOOL isMetaClass1 = class_isMetaClass(currentClass);
NSLog(@"isMetaClass %i", isMetaClass1);
currentClass = object_getClass(currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));

控制台输出

-- ::18.221 XDWRuntimeDemo[:] objcet is MetaClassTest  super object is NSObject
-- ::18.222 XDWRuntimeDemo[:] Following the isa pointer times gives 0x10b144548 //MetaClassTest的地址
-- ::18.222 XDWRuntimeDemo[:] isMetaClass
-- ::18.222 XDWRuntimeDemo[:] Following the isa pointer times gives 0x10b144520 //MetaClassTest元类的地址
-- ::18.222 XDWRuntimeDemo[:] isMetaClass
-- ::18.222 XDWRuntimeDemo[:] Following the isa pointer times gives 0x10baebe08 //根元类的地址(MetaClassTest元类的的isa指向)
   -- ::18.223 XDWRuntimeDemo[:] isMetaClass
   2016-- ::18.223 XDWRuntimeDemo[:] Following the isa pointer times gives 0x10baebe08 //根元类isa指向(就是它本身)
   2016-- ::18.223 XDWRuntimeDemo[:] isMetaClass
   2016-- ::18.223 XDWRuntimeDemo[:] NSObject's class is 0x10baebe58
   2016-- ::18.223 XDWRuntimeDemo[:] NSObject's meta class is 0x10baebe08 //NSObject的元类地址

类与对象操作函数

// 获取类的类名
const char * class_getName ( Class cls );
// 获取类的父类
Class class_getSuperclass ( Class cls ); // 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
// 获取实例大小
size_t class_getInstanceSize ( Class cls );

成员变量(ivars)及属性

// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );//它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。 // 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );//目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。 // 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
//Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。(解决办法,通过runtime关联对象,可以给已存在的类添加实例变量)
//但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用
//不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。
如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))
这个配和下面的动态创建类对象一起使用
 
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );//返回一个指向成员变量信息的数组

属性操作函数

// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name ); // 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount ); // 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount ); // 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

方法(methodLists)

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

/**

class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,

如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,

可以使用method_setImplementation。一个Objective-C方法是一个简单的C函数,

它至少包含两个参数—self和_cmd。与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。

*/

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name ); // 获取类方法
Method class_getClassMethod ( Class cls, SEL name ); // 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount ); // 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types ); // 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name ); // 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
  • class_getInstanceMethod、class_getClassMethod函数,与class_copyMethodList不同的是,这两个函数都会去搜索父类的实现。

  • class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。

  • class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。

  • class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。

  • class_respondsToSelector函数,我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。

协议(objc_protocol_list)

协议相关的操作包含以下函数:

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol ); // 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol ); // 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
  • class_conformsToProtocol函数可以使用NSObject类的conformsToProtocol:方法来替代。

  • class_copyProtocolList函数返回的是一个数组,在使用后我们需要使用free()手动释放。

版本(version)

// 获取版本号
int class_getVersion ( Class cls ); // 设置版本号
void class_setVersion ( Class cls, int version );

其它 通常我们不直接使用这两个函数。

Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );

动态创建类和对象

动态创建类

// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
//objc_allocateClassPair函数:如果我们要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数 // 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls );
//用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法 // 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );

class_addMethod,class_addIvar ,class_addProperty 等函数来为新创建的类添加方法、实例变量和属性等 完事就可以使用这个新类了。

注意

实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。

动态创建对象 //这三个函数全部不能在ARC下使用

// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
//class_createInstance函数:创建实例时,会在默认的内存区域为类分配内存。extraBytes参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量 // 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes ); // 销毁类实例
void * objc_destructInstance ( id obj );

实例操作函数

// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size ); // 释放指定对象占用的内存
id object_dispose ( id obj );

有这样一种场景,假设我们有类A和类B,且类B是类A的子类。类B通过添加一些额外的属性来扩展类A。现在我们创建了一个A类的实例对象,并希望在运行时将这个对象转换为B类的实例对象,这样可以添加数据到B类的属性中。这种情况下,我们没有办法直接转换,因为B类的实例会比A类的实例更大,没有足够的空间来放置对象。此时,我们就要以使用以上几个函数来处理这种情况,

NSObject *a = [[NSObject alloc] init];NSObject是A类
id newB = object_copy(a, class_getInstanceSize(MyClass.class));MyClass是B类
object_setClass(newB, MyClass.class);//将newb转换成MyClass类对象

 object_dispose(a);

2.针对对象实例变量进行操作的函数,: 那么调用object_getIvar会比object_getInstanceVariable函数快,相同情况下,object_setIvar也比object_setInstanceVariable快

// 修改类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value ); // 获取对象实例变量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue ); // 返回指向给定对象分配的任何额外字节的指针
void * object_getIndexedIvars ( id obj ); // 返回对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar ); // 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );

3.针对对象的类进行操作的函数

// 返回给定对象的类名
const char * object_getClassName ( id obj ); // 返回对象的类
Class object_getClass ( id obj ); // 设置对象的类
Class object_setClass ( id obj, Class cls );

获取类定义 在这些类上调用方法是,都应该先检测一下这个方法是否在这个类中实现。

// 获取已注册的类定义的列表
int objc_getClassList ( Class *buffer, int bufferCount ); // 创建并返回一个指向所有已注册类的指针列表
Class * objc_copyClassList ( unsigned int *outCount ); // 返回指定类的类定义
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name ); // 返回指定类的元类
Class objc_getMetaClass ( const char *name );
  • 获取类定义的方法有三个:objc_lookUpClass, objc_getClass和objc_getRequiredClass。如果类在运行时未注册,则objc_lookUpClass会返回nil,而objc_getClass会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass函数的操作与objc_getClass相同,只不过如果没有找到类,则会杀死进程。

  • objc_getMetaClass函数:如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。

小结:这一部分主要介绍了runtime中类与对象相关的数据结构,通过丰富的函数,我们可以窥探OC底层的一小部分信息。这些丰富的函数,可以灵活的对数据进行各种操作。当然比较常用的也就一小部分,毕竟runtime也只是用很少一部分,但是了解学习runtime确实是能让自己的开发和编程的理解能力得到了提升,扩展了视野。