前言
等风来不如追风去,总有那么一个人在这风景正好的季节来到你的身边。在上一篇探究了对象
的本质和isa
指针的底层,那么我们继续来看类
的底层结构。
补充知识
- 在OC环境下使用的类,在底层都有替换的类去实现
isa 和类的关联
类的isa
探索对象的时候我们已经知道,对象的结构体中的isa
指向的是类
,这个时候就会想,类
也是一个对象,类
中也有isa
,那么类
的isa
又指向哪里呢?如下图:
JCPerson
类对应了两个内存地址0x00000001000085f0
和0x00000001000085c8
,哪一个才是JCPerson
的地址呢?一个类
在内存中存在几个内存地址
呢?接下来看下图:
类的地址.png
看到这里应该非常清晰了,一个
类
只有一个内存地址,0x00000001000085f0
是JCPerson
的类地址,而0x00000001000085c8
这个类地址苹果把它叫做元类
总结:
-
元类
由系统编译器自动生成和编译,与创建者无关 -
对象
的isa
指向类
,类对象
的isa
指向元类
isa的走位图
上面我们已经分析了对象
、类
的isa
的走向,那么元类
的isa
又指向哪里呢?结合LLDB来进行探索。
- 对象
isa
--> 类isa
--> 元类isa
--> 根元类isa
--> 根元类(自己) - 根类(
NSObject
)isa
--> 根元类isa
--> 根元类(自己)
isa
的流程图:
isa走位图.png
类、元类、根元类继承图
创建JCPerson
,JCTeacher
,NSObject
的相关代码,探究一下它们的继承
关系。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%@",class_getSuperclass(JCTeacher.class));
NSLog(@"%@",class_getSuperclass(JCPerson.class));
NSLog(@"%@",class_getSuperclass(NSObject.class));
# NSObject实例对象
NSObject *object1 = [NSObject alloc];
# NSObject类
Class class = object_getClass(object1);
# NSObject元类
Class metaClass = object_getClass(class);
# NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
# NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\nNSObject实例对象 %p\nNSObject类 %p\nNSObject元类 %p\nNSObject根元类 %p\nNSObject根根元类 %p",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
# NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
# 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
# JCPerson元类和元类的父类
Class pMetaClass = object_getClass(JCPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
# JCTeacher元类和元类的父类
Class tMetaClass = object_getClass(JCTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
}
return 0;
}
类、元类的继承关系.png
源码分析中知道,
NSObject
的父类打印结果是nil
;NSObject
根元类的父类的地址等于NSObject
类的地址;JCPerson
元类的父类的地址等于NSObject
的元类。
-
JCTeacher
-->JCPerson
-->NSObject
-->nil
-
JCTeacher元类
-->JCPerson元类
-->NSObject元类
-->NSObject根元类
-->NSObject
类
的继承图:
类的继承图.png
isa
流程图和继承链:
isa流程图和继承图.png
内存偏移
在前面探究对象
的底层实现,我们了解到对象属性
的getter
方法底层实现是通过首地址
+内存偏移
的方式去获取内存中的变量。接下来我们来看一下内存偏移
。
基本指针
# 普通指针
int a = 10; //
int b = 10; //
JCNSLog(@"%d -- %p",a,&a);
JCNSLog(@"%d -- %p",b,&b);
==========: 10 -- 0x7ffeefbff4ec
==========: 10 -- 0x7ffeefbff4e8
-
a
的地址是0x7ffeefbff4ec
,b
的地址是0x7ffeefbff4e8
,相差4个字节,int
类型是4个字节的长度 -
a
>b
的地址,从高地址
往低地址
偏移,这符合栈内存
的分配原则
对象指针
# 对象
JCPerson *p1 = [JCPerson alloc];
JCPerson *p2 = [JCPerson alloc];
JCNSLog(@"%@ -- %p",p1,&p1);
JCNSLog(@"%@ -- %p",p2,&p2);
==========: <JCPerson: 0x1004075a0> -- 0x7ffeefbff4e8
==========: <JCPerson: 0x100408570> -- 0x7ffeefbff4e0
-
alloc
开辟的内存在堆区
,指针地址
在栈区
-
堆区
是从低
地址 -->高
地址,栈区
是从高
地址 -->低
地址
数组指针
int c[4] = {1,2,3,4};
int *d = c;
JCNSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
JCNSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
int value = *(d+i);
JCNSLog(@"----%d",value);
}
==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e0 - 0x7ffeefbff4e4
==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e4 - 0x7ffeefbff4e8
==========: ----1
==========: ----2
==========: ----3
==========: ----4
- 数组的地址就是元素的
首地址
,即&c == &c[0]
- 数组中每个元素的地址可以通过:
首地址 + n*元素类型大小
来获取,只需要数组中元素数据类型
相同 - 数组中的每个元素的
地址间隔
是通过当前元素的数据类型
决定的
总结:
- 内存偏移可以根据
首地址
+偏移值
方式来获取各个数据的内存地址
类结构的分析
上面我们已经了解了内存偏移
的知识,接下来我们来探究类
的底层结构。
对象
底层结构中存放在属性
、成员变量
等数据;从图中打印可以看出类
是有内存的,那么它里面存放着什么呢?接下来分析类
底层的数据结构。
类的底层结构:
探索对象isa
的过程中,我们已经知道isa
在底层是Class
类型,Class
类型是objc_class *
,所有类
的底层实现都是objc_class
,通过全局搜索可以找到如下代码:
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; # OBJC2不可用
通过上面这段代码我们发现在OBJC2
中不可用,而现在的版本基本是在用OBJC2
,所以这不是我们分析的,接下来在看一下如下代码:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
...
下面全部是方法,不需要分析,省略
}
源码分析:objc_class
是继承objc_object
,这说明类
也是对象
,所谓万物皆对象。objc_class
里面有一个隐藏成员变量isa
,我们前面已经分析过了,下面还有三个成员变量superclass
,cache
,bits
,我们知道首地址就是isa
,那么我们可以通过首地址
+偏移量
的方式去获取成员变量的地址,然后获取值。
-
isa
是结构体指针,占8
字节 -
Class superclass
是Class
类型,也是属于结构体指针,占8
字节 -
cache
是cache_t
结构体,结构体大小由内部的变量决定 -
bits
是class_data_bits_t
结构体,如果知道前面三个成员变量的大小,那么就可以得到bits
的地址
前面三个成员变量已经知道了前两个的大小,只要知道cache
的内存大小,接下来看一下cache_t
的内存大小
typedef unsigned long uintptr_t;
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
};
...
下面的一些方法直接省略(static类型的是在内存的全局区,不在结构体里面的内存)
}
cache_t
是一个结构体类型,内部包含了_bucketsAndMaybeMask
和一个联合体
.
-
_bucketsAndMaybeMask
是uintptr_t
类型,而uintptr_t
是无符号长整型占8个字节 -
联合体
内存大小由成员变量中的最大变量的内存大小决定,该联合体由一个结构体
和_originalPreoptCache
两个成员变量组成,由于联合体存在互斥的,所以只需要得到其中最大变量的内存大小 -
_originalPreoptCache
是preopt_cache_t *
结构体指针类型,占8个字节 - 结构体中有
_maybeMask
,_flags
,_occupied
。_maybeMask
是mask_t
类型,mask_t
又是uint32_t
类型,占4个字节,_flags
和_occupied
是uint16_t
类型,占2个字节
综上所述cache_t
的内存大小为16
字节。
总结:
-
isa
内存地址为首地址
-
superclass
地址为首地址
+0x08
-
cache_t
地址为首地址
+0x10
-
bits
地址为首地址
+0x20
类的结构图.png
bits数据结构
上面已经了解类
的基本结构,isa
和superclass
已经探究过了,接下来我们先来研究一下成员变量bits
存储了哪些信息?
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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 getSuperclass() const {...}
void setSuperclass(Class newSuperclass) {...}
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
bits
是class_data_bits_t
类型,底层源码中还有一个data()
,返回bits.data()
,这有可能就是bits
中存储的数据。data()
的类型是class_rw_t
。
struct class_rw_t {
... //省略一些没用的方法
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
class_rw_t
是一个结构体,里面存储着方法
,属性
,协议
列表,接下来验证是否存储在class_rw_t
中。
属性探究( properties() )
@interface JCPerson : NSObject{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
@end
类结构中属性分析.png
-
property_list_t
中存储name
,hobby
属性 -
p $7.get(2)
会提示数组越界,没有找到成员变量subject
问题:那么定义的subject
成员变量存到哪里去了呢?
补充:成员变量
class_rw_t
中除了有属性
,方法
,协议
以外,还有class_ro_t
结构体指针类型的ro()
,在class_ro_t
结构体中我们可以找到ivar_list_t
类型的指针ivars
,成员变量
会不会存在这里呢?
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
类结构中成员变量分析.png
源码和LLDB分析:
- 成员变量底层实现是
ivar_t
,存储在class_ro_t
成员变量列表 - 系统是自动给
属性
添加_属性名
的变量,存储在class_ro_t
成员变量列表
方法探究( methods() )
类结构的方法分析.png通过
LLDB
的方式,可以得到定义的方法
。但是发现使用get(index)
的方式无法得到,�使用get(index).big()
才能获取,这是为什么呢?
struct property_t {
const char *name;
const char *attributes;
};
struct method_t {
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
SEL name() const {
if (isSmall()) {
return (small().inSharedCache()
? (SEL)small().name.get()
: *(SEL *)small().name.get());
} else {
return big().name;
}
}
const char *types() const {
return isSmall() ? small().types.get() : big().types;
}
IMP imp(bool needsLock) const {
if (isSmall()) {
IMP imp = remappedImp(needsLock);
if (!imp)
imp = ptrauth_sign_unauthenticated(small().imp.get(),
ptrauth_key_function_pointer, 0);
return imp;
}
return big().imp;
}
源码分析:
属性和方法获取区别.png
-
属性
底层实现是property_t
,在property_t
结构体中定义了name
等变量 -
方法
底层实现是method_t
,在method_t
结构体中定义了一个big()
,通过big()
获取SEL
和IMP
接下来我们继续打印methods
,如下图。
-
method_list_t
中有对象方法
,属性的setter方法
和getter方法
-
method_list_t
中没有获取到类方法
问题:那么类方法
存储到哪里去了呢?
补充:类方法
对象方法
存储在类
中,那类方法
可能存储在元类
。
-
object_getClass
获取到JCPerson
的元类 - 元类中
method_list_t
中存储着类方法
总结:
- 类的结构主要由
isa
,superclass
,cache
,bits
组成 -
bits
中存储着属性
,方法
,协议
-
属性
存储在property_list_t
中,而成员变量
存储在class_ro_t
-->ivar_list_t
,系统为属性
自动生成的_属性名
的变量也存储在class_ro_t
-->ivar_list_t
-
方法
存储在method_list_t
中,method_list_t
主要存储着对象方法
,属性的setter方法
和getter方法
,而类方法
存储在元类
中的method_list_t
网友评论