获取类对象中的成员变量
经过上篇文章的探索,我们知道属性列表,实例方法列表以及协议方法列表存放在类对象的class_rw_t这样的一个结构体中,那么成员变量存放在哪呢?我们继续进行探索,我们发现在class_rw_t中有一个返回class_ro_t结构体指针的方法:
我们还是通过上篇文章探索方法列表的方式对成员变量进行探索:
Person类的成员变量和属性如下:
也就是说成员变量存放在
class_ro_t这么一个结构体中。那么为什么要将它们分开存放呢?
class_rw_t,class_ro_t,class_ro_ext_t
class_ro_t的结构体是在编译阶段生成的。当类从磁盘中加载出来的时候,属性,方法和协议方法等一些其他的信息都存在其中,这里边的东西是不可变的。class_rw_t则是在运行时阶段生成的,它会把ro中可能会更改的东西拿过来存储其中。当在运行时阶段,我们可能会动态的添加一些信息,比如方法,属性,这时候就会生成一个叫做class_ro_ext_t的结构体把需要更改的从class_ro_t中提取出来存放在里面。这样一来可变的存在一个地方,不可变的存在一个地方,而且也节省了空间。
苹果设计元类的原因
OC中方法调用的本质就是消息机制objc_msgSend(接收者,方法名),当我们调用方法的时候,系统就会去该接收者的isa指针指向的对象中去查找方法。如果接收者是实例对象,则会去类对象中根据方法名查找方法实现,如果接收者是类对象,则会去元类中进行查找。消息发送最需要的就是速度要快,如果我们在这个方法调用的底层写逻辑判断势必会影响执行速度。这也体现了单一职责,类对象存储实例实例方法以及成员变量等,元类存储类方法。所以苹果设计元类最大的目的就是复用消息机制。
为什么成员变量的值要放在实例对象中,而成员变量放在类对象中。
因为类对象只有一个,而实例对象会有很多个,而这些实例对象的成员变量的值都可能会是不同的。所以类对象就好似一个模板,来规定如何去实例化一个实例对象。
通过runtime api获取成员变量,属性以及方法
类结构如下:
获取成员变量列表
- (void)getIvarList:(Class)cls {
unsigned int outcount = 0;
Ivar *ivars = class_copyIvarList(cls, &outcount);
for (int i = 0; i < outcount; i++) {
Ivar ivar = ivars[i];
const char *cName = ivar_getName(ivar);
const char *cType = ivar_getTypeEncoding(ivar);
NSLog(@"name = %s type = %s", cName, cType);
}
free(ivars);
}
//调用
[self getIvarList:[Person class]];
结果:
2022-04-28 20:44:16.648612+0800 LearnTest[39000:5243491] name = _hobby type = @"NSString"
2022-04-28 20:44:16.648676+0800 LearnTest[39000:5243491] name = _age type = i
2022-04-28 20:44:16.648710+0800 LearnTest[39000:5243491] name = _name type = @"NSString"
获取属性列表
- (void)getIvarList:(Class)cls {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
const char *cName = ivar_getName(ivar);
const char *cType = ivar_getTypeEncoding(ivar);
NSLog(@"name = %s type = %s", cName, cType);
}
free(ivars);
}
// 调用
[self getPropertyList:[Person class]];
结果:
2022-04-28 21:04:44.981982+0800 LearnTest[39110:5258890] name = name attr = T@"NSString",&,N,V_name
2022-04-28 21:04:44.982026+0800 LearnTest[39110:5258890] name = age attr = Ti,N,V_age
获取方法列表
- (void)getMethodList:(Class)cls {
unsigned int outCount = 0;
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
SEL sel = method_getName(method);
NSString *selString = NSStringFromSelector(sel);
const char *type = method_getTypeEncoding(method);
NSLog(@"name = %@, type = %s", selString, type);
}
free(methods);
}
// 调用:
[self getMethodList:[Person class]];
结果:
2022-04-28 21:30:28.098241+0800 LearnTest[39254:5275792] name = instanceMethod, type = v16@0:8
2022-04-28 21:30:28.098297+0800 LearnTest[39254:5275792] name = name, type = @16@0:8
2022-04-28 21:30:28.098353+0800 LearnTest[39254:5275792] name = setName:, type = v24@0:8@16
2022-04-28 21:30:28.098388+0800 LearnTest[39254:5275792] name = age, type = i16@0:8
2022-04-28 21:30:28.098414+0800 LearnTest[39254:5275792] name = setAge:, type = v20@0:8i16
2022-04-28 21:30:28.098449+0800 LearnTest[39254:5275792] name = .cxx_destruct, type = v16@0:8
那么我们要获取类方法呢,只需要将类对象换成元类对象即可:
调用:
[self getMethodList:objc_getMetaClass("Person")];
结果:
2022-04-28 21:39:12.461387+0800 LearnTest[39302:5282575] name = classMethod, type = v16@0:8
我们来看以下的例子,为啥获取类方法通过class_getInstanceMethod也可以获得?
我们来看看
class_getClassMethod的源码:
可以看到,它也调用的是
class_getInstanceMethod方法,只是参数变为了元类而已。所以说明在苹果的底层是不区分实例方法和类方法的,也更能说明苹果设计元类的目的并不只是为了存储类方法。
我们再来看以下的例子,为什么两个找不到方法的两个例子却返回了相同的地址:
我们再去找class_getMethodImplementation的源码:
注意那个_objc_msgForward方法,这个我们将会在消息发送的探索中进行讲解。