一、类的基础数据结构
1. objc_class
OC类是由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指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
- version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
- objc_ivar_list结构体用来存储成员变量的列表,而objc_ivar则存储了单个成员变量的信息;同理,objc_method_list结构体存储着方法数组的列表,而单个方法的信息则由objc_method结构体存储。
但在OC2.0版本中 已经不使用了(#if !OBJC2和OBJC2_UNAVAILABLE)。
在新版中,objc_class继承结构体objc_object,并且结构体内有一些函数(因为是c++结构体,在c上做了扩展,因此结构体中可以包含参数)。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
//方法缓存
cache_t cache; // formerly cache pointer and vtable
//其中只含有一个 64 位的 bits 用于存储与类有关的信息
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
注释掉了Class ISA,是由于继承的objc_object结构体中有isa指针。
(1)class_rw_t和class_rw_t
objc_class结构体内部,class_rw_t是通过bits调用data方法的来的。在data方法内部,将bits与 FAST_DATA_MASK进行位运算,会得到class_rw_t结构体。
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
class_rw_t结构体(rw代表readwrite可读可写,t代表table表)
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
//方法列表
method_list_t *methods;
//属性列表
property_list_t *properties;
//协议列表
const protocol_list_t * protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
指向常量的指针 ro,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。
其中还包含一个struct class_ro_t的结构体(ro代表readonly,只读)
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
//instance对象占用的内存空间
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
//类名
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
//成员变量列表
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
class_ro_t 和 class_rw_t 的分析:
class_rw_t结构体内有一个指向class_ro_t结构体的指针。
每个类都对应有一个class_ro_t结构体和一个class_rw_t结构体。在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。
类的realizeClass运行之前:
realizeClass运行之前
类的realizeClass运行之后:
realizeClass运行之后 细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。
(2)isa指针
在OC中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。
NSString *str = @"aaa";
str本质是一个objc_object结构体,而这个结构体的成员变量isa指针指向了NSString类,NSString类其实是类对象。
struct objc_classs结构体里存放的数据称为元数据(metadata),类对象的isa指针指向的我们称之为元类(meta class)。元类中保存了创建类对象以及类方法所需的所有信息。
isa是一个Class类型的指针,每个实例对象有个isa指针,它指向对象的类,而Class里也有isa指针,指向meteClass(元类)。元类也是类,它也是对象。元类也有isa指针,它的isa指针最终指向一个根元类,根元类的isa指针指向自己本身。
2.objc_object与id
objc_object是表示一个类的实例的结构体
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
这个结构体只有一个类的isa指针。当向一个OC对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。
当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。
另外还有我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。
二、类和对象的操作函数
runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。
1.操作函数
(1)类名
// 获取类的类名
const char * class_getName ( Class cls );
(2)父类(super class)和元类(meta class)
// 获取类的父类
Class class_getSuperclass ( Class cls );
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
-
class_getSuperclass
函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。 -
class_isMetaClass
函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
(3)实例变量大小(instance_size)
// 获取实例大小
size_t class_getInstanceSize ( Class cls );
(4)成员变量(ivars)
在objc_class中,所有的成员变量、属性的信息是放在链表ivars中的。ivars是一个数组,数组中每个元素是指向Ivar(变量信息)的指针。
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
-
class_getInstanceVariable
函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。 -
class_getClassVariable
函数,目前没有找到关于OC中类变量的信息,一般认为OC不支持类变量。注意,返回的列表不包含父类的成员变量和属性。 -
class_addIvar
函数, OC不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果通过运行时来创建一个类的话,就可以使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。 -
class_copyIvarList
函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
不能向编译后得到的类中增加实例变量,因为编译后的类已经注册在Runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,同时Runtime会调用class_setIvarLayout或class_setWeakIvarLayout来处理strong weak 引用,所以不能向存在的类中添加实例变量。
(5)属性(Property)
// 获取指定的属性
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 );
(6)方法(methodLists)
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 获取实例方法
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_addMethod
的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation
。一个OC方法是一个简单的C函数,它至少包含两个参数—self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数。与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。 -
class_copyMethodList
函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)
(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。 -
class_getInstanceMethod
、class_getClassMethod
函数,与class_copyMethodList
不同的是,这两个函数都会去搜索父类的实现。 -
class_replaceMethod
函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod
函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation
一样替代原方法的实现。 -
class_getMethodImplementation
函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。 -
class_respondsToSelector
函数,我们通常使用NSObject类的respondsToSelector:
或instancesRespondToSelector:
方法来达到相同目的。
(7)协议(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()手动释放。
2. 示例
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DJClass : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end
NS_ASSUME_NONNULL_END
@interface DJClass () {
NSInteger _instance1;
NSString * _instance2;
}
@property (nonatomic, assign) NSUInteger integer;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation DJClass
+ (void)classMethod1 {
}
- (void)method1 {
NSLog(@"call method method1");
}
- (void)method2 {
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
DJClass *myClass = [[DJClass alloc] init];
unsigned int outCount = 0;
Class cls = myClass.class;
// 类名
NSLog(@"class name: %s", class_getName(cls));
// 父类
NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));
// 是否是元类
NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));
// 变量实例大小
NSLog(@"instance size: %zu", class_getInstanceSize(cls));
// 成员变量
Ivar *ivars = class_copyIvarList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"instance variable's name: %s at index: %d", ivar_getName(ivar), i);
}
free(ivars);
// 指定名称成员变量信息
Ivar string = class_getInstanceVariable(cls, "_string");
if (string != NULL) {
NSLog(@"instace variable %s", ivar_getName(string));
}
// 属性操作
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSLog(@"property's name: %s", property_getName(property));
}
free(properties);
// 获取指定属性
objc_property_t array = class_getProperty(cls, "array");
if (array != NULL) {
NSLog(@"property %s", property_getName(array));
}
// 方法操作
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
NSLog(@"method's signature: %s", method_getName(method));
}
free(methods);
// 获取实例方法
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
NSLog(@"method %s", method_getName(method1));
}
// 获取类方法
Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
if (classMethod != NULL) {
NSLog(@"class method : %s", method_getName(classMethod));
}
// 类实例是否响应指定的selector
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"" : @" not");
// 方法的具体实现
IMP imp = class_getMethodImplementation(cls, @selector(method1));
imp();
// 协议
Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
Protocol * protocol;
for (int i = 0; i < outCount; i++) {
protocol = protocols[i];
NSLog(@"protocol name: %s", protocol_getName(protocol));
}
// 类是否实现指定的协议
NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
三、动态创建类和对象
1.动态创建类
// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls );
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );
-
objc_allocateClassPair
函数:如果我们要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
为了创建一个新类,我们需要调用objc_allocateClassPair
。然后使用诸如class_addMethod
,class_addIvar
等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair
函数来注册类,之后这个新类就可以在程序中使用了。
实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。 -
objc_disposeClassPair
函数用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法。
#import "ViewController.h"
#import <objc/runtime.h>
#import "DJClass.h"
@interface ViewController ()
@end
@implementation ViewController
-(void)viewDidLoad{
[super viewDidLoad];
Class cls = objc_allocateClassPair([DJClass class], "DJSubClass", 0);
class_addMethod(cls, @selector(method3), (IMP)imp_Method3, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);
id instance = [[cls alloc] init];
[instance performSelector:@selector(method3)];
[instance performSelector:@selector(method1)];
}
void imp_Method3(id self,SEL _cmd){
NSLog(@"call method method3");
}
void imp_submethod1(id self,SEL _cmd){
NSLog(@"call method submethod1");
}
@end
//输出结果
2021-07-22 13:45:11.529352+0800 DJTestDemo[3314:98781] call method method3
2021-07-22 13:45:11.529470+0800 DJTestDemo[3314:98781] call method submethod1
2.动态创建对象
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
-
class_createInstance
函数:创建实例时,会在默认的内存区域为类分配内存。extraBytes参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。该函数在ARC环境下无法使用。
调用class_createInstance
的效果与+alloc方法类似。区别看下面的示例:
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);
//输出结果
2021-07-22 13:57:12.877791+0800 DJTestDemo[3444:105852] NSString
2021-07-22 13:57:12.877925+0800 DJTestDemo[3444:105852] __NSCFConstantString
可以看到,使用class_createInstance
函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString。
-
objc_constructInstance
函数:在指定的位置(bytes)创建类实例。 -
objc_destructInstance
函数:销毁一个类的实例,但不会释放并移除任何与其相关的引用。
四、实例操作函数
实例操作函数主要是针对创建的实例对象的一系列操作函数。
1.针对整个对象进行操作的函数
// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );
以上两个函数不能在ARC下使用。
2.针对对象实例变量进行操作的函数
// 修改类实例的实例变量的值
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 );
如果实例变量的Ivar已经知道,那么调用object_getIvar
会比object_getInstanceVariable
函数快,相同情况下,object_setIvar
也比object_setInstanceVariable
快。
3.针对对象的类进行操作的函数
// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );
// 设置对象的类
Class object_setClass ( id obj, Class cls );
五、获取类定义
OC动态运行库会自动注册我们代码中定义的所有的类。我们也可以在运行时创建类定义并使用objc_addClass
函数来注册它们。runtime提供了一系列函数来获取类定义相关的信息,这些函数主要包括:
// 获取已注册的类定义的列表
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_getClassList
函数:作用是获取已经注册的类,它需要传入两个参数,第一个参数 buffer :已分配好内存空间的数组,第二个参数 bufferCount:数组中可存放元素的个数,返回值是注册的类的总数。利用这个函数也可以获取到某一个类的所有子类。 - 获取类定义的方法有三个:
objc_lookUpClass
,objc_getClass
和objc_getRequiredClass
。如果类在运行时未注册,则objc_lookUpClass
会返回nil,而objc_getClass
会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass
函数的操作与objc_getClass
相同,只不过如果没有找到类,则会杀死进程。 -
objc_getMetaClass
函数:如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
网友评论