在上一篇文章iOS-Objective-C的本质中,笔者主要分析了三个面试题中的第一个问题。然后针对它做了一个稍微深入的分析,这也算是对OC对面的内存分配的一个小总结
接下来我们再来看看剩余的两个问题,首先我再次把问题抛出来:
2、对象的isa指针指向哪里
3、OC的类信息存放在哪里
首先,笔者先给出这两个问题的答案,这样要是之前已经了解过的同学可以直接总结回顾,要是了解的不是很全面的同学,我们在一起具体分析下:
问题一:对象的isa指针指向哪里
答:instance(实例)对象的isa指向class(类)对象;class对象的isa指向meta-class(原类)对象;meta-class类的isa指向基类的meta-class对象。
问题二:OC的类信息存放在哪里
答:对象方法、属性、成员变量、协议信息,存放在class对象中;类方法,存放在meta-class对象中;成员变量的具体值,存放在instance对象中
其实针对于上面这些回答,有一张很好的图可以说明,如下:
接下来,我们一步一步来分析这几个结论是怎么出来的,我们先来看看如下代码:
@interface BLPerson : NSObject
@end
@implementation BLPerson
@end
int main(int argc, const char * argv[]) {
BLPerson *person = [[BLPerson alloc] init];
Class personClass = object_getClass(person);
NSLog(@"person --- %p", person);
NSLog(@"personClass --- %p", personClass);
return 0;//此处打断点,观察person和personClass的情况
}
我们看看控制台的打印:
image.png
大家应该可以看到两个红色框框中是同一个地址;先说明下,p/x 这个命令行就是打印16进制书,把isa先强制转换成long,然后在用16进制打印,这样我们看到的就是熟悉的地址形式。
当然心细的同学发现了笔者在打印地址之后,做了一个&操作,二在&0x00007ffffffffff8之后,得到的结果就是我们类对象的地址,这是为什么呢?
其实在很早的时候,instance对象的isa确实直接指向class对象地址,不过后来苹果公司加了一层&ISA_MASK操作,就是在instance对象isa基础上& ISA_MASK之后,得到的结果就是class对象地址,这个ISA_MASK宏可以在源码objc-4中找到,在不同系统下,它对应的值是不一样的,源码地址,在上一篇文章中笔者已经发过了。
接下来,我们在根据这个方法,来看看class对象中的isa指针是否指向meta-class的地址,代码如下:
BLPerson *person = [[BLPerson alloc] init];//实例对象
Class personClass = object_getClass(person);//类对象
Class personMetaClass = object_getClass(personClass);//元类对象
NSLog(@"person --- %p", person);
NSLog(@"personClass --- %p", personClass);
NSLog(@"personMetaClass --- %p", personMetaClass);
image.png
这个时候,大家会发现,打印不出来isa的地址;这里我们做一个对象和结构体的转换。在上一篇文章中,我们就知道,其实对象的本质就是一个结构体,根据这个原理,我们代码做一下改变:
struct BLPerson_OBJC {
Class isa;
};
int main(int argc, const char * argv[]) {
BLPerson *person = [[BLPerson alloc] init];//实例对象
Class personClass = object_getClass(person);//类对象
struct BLPerson_OBJC *personClass_Struct = (__bridge struct BLPerson_OBJC *)(personClass);
Class personMetaClass = object_getClass(personClass);//元类对象
NSLog(@"person --- %p", person);
NSLog(@"personClass --- %p", personClass);
NSLog(@"personMetaClass --- %p", personMetaClass);
return 0;
}
这个时候我们在重新打印类对象的isa指向:
image.png
可以很清晰的看到,对象的isa&ISA_MASK之后,就是原类对象的地址
所以元类对象的isa指针& ISA_MASK之后,就是基类的meta-class对象地址,这个结论大家可以照上面的方法去分析,比方说,我们上面的BLPerson是继承自NSObject这个基类的,所以,BLPerson的元类对象isa& ISA_MASK之后就是NSObject元类对象的地址,这里具体我们就不做展开了
接下来,我们来看看最后一个问题:OC的类信息存放在哪里
答:对象方法、属性、成员变量、协议信息,存放在class对象中;类方法,存放在meta-class对象中;成员变量的具体值,存放在instance对象中
当然看过runtime类的代码的同学,应该有所了解
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
在objc_class结构体中其实包含了,一个类的很多信息,我们也可以通过runtime运行时机制,去获取它的诸多信息,这里笔者通过自己所看的资料来总结下。
其实上面的代码中,大家可以看到#if !OBJC2这个if语句,说明,其实在最新版本的底层代码中,objc_class是不直接暴露if里面所包含的name、version、info等信息的,那这个objc_class结构体真正的样子是怎么样的呢?我们还是可以通过源码去了解,其实它的源码是这样的:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
assert(isFuture() || isRealized());
data()->setFlags(set);
}
......
}
我只列举了一小部分,大家会发现,objc_class是继承自objc_object,而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();
......
}
在objc_class这个结构体中,我们可以看到class_rw_t这个结构体,它内部结构是这样子的:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
......
}
到此,大家就了解到一个类的isa,属性信息,方法信息,协议信息,在底层代码中真正的分布情况,这个源代码还是在objc-4中。
接下来我们还是用代码来看看类对象和元类对象中的信息,这个代码我就直接用资料代码了,到时候我会分享链接地址,主要还是封装了底层代码,有便于我们分析问题:
// main 文件中的主要代码,对应的类申明都在main文件中,大家可以下来源代码查看
MJStudent *stu = [[MJStudent alloc] init];
stu->_weight = 10;
mj_objc_class *studentClass = (__bridge mj_objc_class *)([MJStudent class]);
mj_objc_class *personClass = (__bridge mj_objc_class *)([MJPerson class]);
class_rw_t *studentClassData = studentClass->data();
class_rw_t *personClassData = personClass->data();
class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
class_rw_t *personMetaClassData = personClass->metaClass()->data();
控制台打印如下:
image.png
这张图显示的是类对象中所包含的对象方法、属性信息、协议信息等
image.png
这张图显示的是元类对象中所包含的类方法等信息。
别的信息,大家可以根据自己想看的在代码中了解,当然“成员变量的具体值,存放在instance对象中”这个结论笔者就不分析了,在上一篇文章中,大家就了解到了。
以上就是笔者对最近所看的资料的总结,也希望对大家有所帮助,要是笔者有问题的地方,同学们尽管提,相互交流
网友评论