本文是参考南峰子Objective-C Runtime系列文章
做一个自己的总结,强烈推荐查看原文
1、runtime介绍
Objective-C
是一门动态语言,它将很多静态语言在编译和连接时期做的事情放到了运行时来处理,这种动态语言的优势在于:我们写代码时更具有灵活性,如我们可以把消息转发给我们想要转发的对象,或者随意交换一个方法等。
这种特性意味着Objective-C
不仅需要一个编译器,还需要一个运行时动态库来执行编译的代码。对于Objective-C
来说,这个运行时系统就像一个操作系统一样:让所有工作正常运行。这个运行时系统就是Objc Runtime
,Objc Runtime
其实是一个Runtime
库,基本上是用C语言
和汇编语言
写的,使得C语言有了面向对象的能力。
Runtime
库主要做了以下两件事:
- 封装:在这个库中,对象可以使用
C语言
中的结构体
表示,方法可以使用C函数
来实现,另外再加上一些额外的特性,这些结构体和函数被runtime
函数封装后,我们就可以在程序运行时创建、检查、修改类、对象、方法了 - 找出方法的最终执行代码:当程序执行
[object doSomething]
时,会向接受者(object
)发送一条消息(doSomething
),runtime
会根据消息接受者能否响应该消息而做出不同反应
Objective-C runtime
目前有两个版本:Modern runtime
和Legacy runtime
。Modern Runtime
覆盖了64位的Mac OS X Apps
,还有iOS Apps
,Legacy Runtime
是早期用来给32位 Mac OS X Apps
用的,已经过时
苹果和GNU各自维护一个开源的 runtime 版本,这两个版本之间都在努力的保持一致。
高级编程语言想要成为可执行文件,需要先编译成汇编语言,再汇编为机器语言,机器语言是计算机唯一能识别的语言(转换过程待研究)
2、类
Objective-C
类是有Class
类型表示,实际上是指向objc_class
结构体的指针,定义如下
typedef struct objc_class *Class;
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;
1.isa
: 需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class
里面也有一个isa
指针,它指向metaClass
(元类)
-
super_class
:指向该类的父类,如果该类已经是最顶层的根类(如NSObject
或NSProxy
),则super_class
为NULL
-
name
: 类名 -
version
:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。 -
info
: 类信息 -
instance_size
: 该类的对象大小 -
ivars
: 成员变量的列表 -
methodLists
: 方法列表 -
cathe
: 用于缓存最近使用的方法。
一个接收者对象接收到一个消息时,它会根据isa
指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists
中遍历一遍,性能势必很差。这时,cache
就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache
列表中,下次调用的时候runtime
就会优先去cache
中查找,如果cache
没有,才去methodLists
中查找方法。这样,对于那些经常用到的方法的调用,提高了调用的效率。 -
protocols
: 协议列表
3、对象
objc_object
是表示一个类的实例的结构体
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
这个结构体只有一个属性,即指向其类的isa
指针。这样,当我们向一个Objective-C
对象发送消息时,运行时库会根据实例对象的isa
指针找到这个实例对象所属的类。Runtime
库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector
指向的方法,找到后即运行这个方法。
4、元类(Meta Class)
meta-class是一个类对象的类
当我们向一个对象发送消息时,runtime
会在这个对象所属的这个类的方法列表中查找方法;
而向一个类发送消息时,会在这个类的meta-class
的方法列表中查找方法
meta-class
之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class
,因为每个类的类方法基本不可能完全相同
meta-class
也是一个类,也可以向它发送一个消息,那么它的isa
又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C
的设计者让所有的meta-class
的isa
指向基类的meta-class
,以此作为它们的所属类。
即,任何NSObject
继承体系下的meta-class
都使用NSObject
的meta-class
作为自己的所属类,而基类的meta-class
的isa
指针是指向它自己。这样就形成了一个完美的闭环。
对于NSObject
继承体系来说,其实例方法
对体系中的所有实例、类和meta-class
都是有效的;而类方法
对于体系内的所有类和meta-class
都是有效的。
5、objc_cache
cathe
用于缓存调用过的方法。这个字段是一个指向objc_cache
结构体的指针,其定义如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE;
};
-
mask
:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,runtime
使用这个字段来确定开始线性查找数组的索引位置。指向方法selecto
r的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。 -
occupied
:一个整数,指定实际占用的缓存bucket
的总数。 -
buckets
:指向Method
数据结构指针的数组。这个数组可能包含不超过mask+1
个元素。需要注意的是,指针可能是NULL
,表示这个缓存bucket
没有被占用,另外被占用的bucket
可能是不连续的。这个数组可能会随着时间而增长。
6、类与对象的操作函数
类的操作方法大部分是以class_
为前缀的
对象的操作方法大部分是以objc_
或object_
为前缀
在runtime.h
里面搜索Working with
,一共有8个搜索结果,搜索顺序分别是:
Working with Instances
Working with Classes
Working with Methods
Working with Instance Variables
Working with Properties
Working with Protocols
Working with Libraries
Working with Selectors
这里我们主要看Working with Classes
、Working with Instances
、 Working with Instance Variables
里面的方法
6.1、动态创建类
//创建一个新类和元类
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes) ;
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair(Class _Nonnull cls) ;
//销毁一个类极其相关类
Class objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name,
size_t extraBytes);
-
objc_allocateClassPair
函数:如果我们要创建一个根类,则superclass
指定为Nil
。extraBytes
通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。 -
objc_disposeClassPair
函数用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法。
创建一个新类,先用objc_allocateClassPair
,调用class_addMethod
,class_addIvar
等函数来为新创建的类添加方法、实例变量和属性等。再调用objc_registerClassPair
函数来注册类,之后这个新类就可以在程序中使用了。
实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上
6.2、动态创建对象
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
-
class_createInstance
:创建实例时,会在默认的内存区域为类分配内存。extraBytes
额外的字节可以用来存储类定义中定义之外的其他实例变量. -
objc_constructInstance
:在指定的位置(bytes
)创建类实例。 -
objc_destructInstance
:销毁一个类的实例,但不会释放并移除任何与其相关的引用。
类和对象的相关操作函数
// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );
// 修改类实例的实例变量的值
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 );
// 返回给定对象的类名
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 );
6.3、父类(super_class
)和元类(meta-class
)
// 获取类的父类
Class class_getSuperclass (Class _Nullable cls);
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass (Class _Nullable cls);
6.4、类名
// 获取类的类名
const char * class_getName (Class _Nullable cls);
6.5、版本号
// 获取版本号
int class_getVersion (Class _Nullable cls) ;
// 设置版本号
void class_setVersion (Class _Nullable cls, int version);
6.6、实例大小
// 获取实例大小
size_t class_getInstanceSize (Class _Nullable cls);
6.7 成员变量
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable (Class _Nullable cls, const char * _Nonnull name);
// 获取类成员变量的信息
Ivar class_getClassVariable(Class _Nullable cls, const char * _Nonnull name);
// 添加成员变量
BOOL class_addIvar (Class _Nullable cls, const char * _Nonnull name, size_t size,
uint8_t alignment, const char * _Nullable types) ;
// 获取整个成员变量列表
Ivar * class_copyIvarList (Class _Nullable cls, unsigned int * _Nullable outCount) ;
-
class_getInstanceVariable
: 根据name
返回指定的对象成员变量信息(objc_ivar
结构体)
-
class_getClassVariable
: 根据name
返回指定的类成员变量信息(objc_ivar
结构体),一般认为Objective-C
不支持类变量 -
class_addIvar
: 参数分别是,成员变量所属类、成员变量名、对齐方式、成员变量类型
Objective-C
不支持往已存在的类中添加实例成员变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量,但是动态创建类的可以添加成员变量,只能在objc_allocateClassPair
与objc_registerClassPair
之间
如添加一个NSString
类型的ivar1
变量
class_addIvar(cls, "ivar1", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
-
class_copyIvarList
: 它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar
结构体的指针,outCount
指针返回数组的大小
注意 : 返回的列表不包含父类的成员变量和属性,必须使用free()来释放这个数组
6.8 属性
// 获取指定的属性
objc_property_t class_getProperty(Class _Nullable cls, const char * _Nonnull name);
// 获取属性列表
objc_property_t class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount);
// 为类添加属性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount);
// 替换类的属性
void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount)
-
class_getProperty
: 根据name
返回指定的类属性(objc_property
结构体) -
class_copyPropertyList
: 它返回一个指向属性的数组,数组中每个元素是指向该属性的objc_property
结构体的指针,outCount指针返回数组的大小
注意 : 返回的列表不包含父类的属性,必须使用free()来释放这个数组
-
class_addProperty
: 参数含义分别是:类名、属性名、属性特性、属性特性个数
如添加属性名为@property (nonatomic , copy) NSString *property1;
的属性
objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t nonatomic = { "N", "" }; //nonatomic
objc_property_attribute_t backingivar = { "V", "_property1" };//V 实例变量
objc_property_attribute_t attrs[] = { type, ownership,nonatomic, backingivar };
class_addProperty(cls, "property1", attrs, 4);
-
class_replaceProperty
: 替换类的属性
6.9、方法
//添加方法
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types);
//获取实例方法
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
//获取类方法
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
//获取所有方法数组
Method class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount);
//替代方法的实现
IMP class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types) ;
//返回方法的具体实现
IMP class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) ;
IMP class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) ;
//对象是否响应指定的selector
BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) ;
1.class_addMethod
: 参数含义分别是:添加方法的类、添加方法的编号、方法实现、方法类型,使用这个方法添加方法会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation
如添加一个eat
方法
class_addMethod([self class],@selector(eat:),(IMP) eatIMP, "v@:@");
-
class_getInstanceMethod
: 根据name
返回指定的对象方法(objc_method
结构体) -
class_getClassMethod
: 根据name
返回指定的类方法(objc_method
结构体)
注意 : 这两个函数都会去搜索父类的实现
-
class_copyMethodList
: 返回一个指向实例方法的数组,数组中每个元素都是该方法信息的objc_method
结构体指针,outCount
返回数组的大小。
如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)
,因为一个类的实例方法是定义在元类里面
注意 : 返回的列表不包含父类的方法,必须使用free()来释放这个数组
-
class_replaceMethod
: 如果类中不存在name
指定的方法,则类似于class_addMethod
函数一样会添加方法;如果类中已存在name
指定的方法,则类似于method_setImplementation
一样替代原方法的实现。 -
class_getMethodImplementation
: 返回一个指向方法实现函数的指针,在向类实例发送消息时会被调用,这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函数指针可能是一个指向runtime
内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector
,则返回的函数指针将是运行时消息转发机制的一部分。 -
class_respondsToSelector
: 我们通常使用NSObject
类的respondsToSelector:
或instancesRespondToSelector:
方法来达到相同目的。
6.10、协议
// 添加协议
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
: 返回一个指向协议的数组,数组中每个元素都是该协议信息的objc_object
结构体指针,outCount返回数组的大小。
注意 : 返回的列表不包含父类的协议,必须使用free()来释放这个数组
网友评论