忙不是不学习的借口
在isa和类的关联中我们知道isa中存储着类信息,今天我们就来探索一下类与类的结构。
准备工作
- 自定义一个继承
NSObject的类Person
//.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject{
NSString *name;
}
@property (nonatomic, strong) NSString * hobby;
+ (void)sayHello;
- (void)sayGood;
@end
//.m文件
#import "Person.h"
@implementation Person
+ (void)sayHello {
}
- (void)sayGood {
}
@end
- 继承
Person的类Student
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Student : Person
@end
NS_ASSUME_NONNULL_END
#import "Student.h"
@implementation Student
@end
元类
通过上篇文章,我们知道isa & ISA_MASK可以得到类信息。
我们实例化一个 Person对象kevin,来摸一下isa研究一下。
(lldb) p/x kevin
(Person *) $0 = 0x00000001004b2180
(lldb) po 0x00000001004b2180
<Person: 0x1004b2180>
(lldb) x/4gx 0x00000001004b2180
0x1004b2180: 0x001d80010000848d 0x0000000000000000
0x1004b2190: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d80010000848d
-dßd�
(lldb) p/x 0x001d80010000848d & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100008488
(lldb) po 0x0000000100008488
Person
(lldb) x/4gx 0x0000000100008488
0x100008488: 0x0000000100008460 0x00007fff9497d118
0x100008498: 0x0000000100407610 0x0004802400000007
(lldb) po 0x0000000100008460
Person
(lldb) p/x 0x0000000100008460 & 0x00007ffffffffff8ULL
(unsigned long long) $6 = 0x0000000100008460
(lldb) po 0x0000000100008460
Person
(lldb) x/4gx 0x0000000100008460
0x100008460: 0x00007fff9497d0f0 0x00007fff9497d0f0
0x100008470: 0x0000000100705bb0 0x0003e03500000007
(lldb) po 0x00007fff9497d0f0
NSObject
(lldb) p/x 0x00007fff9497d0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00007fff9497d0f0
(lldb) po 0x00007fff9497d0f0
NSObject
(lldb) x/4gx 0x00007fff9497d0f0
0x7fff9497d0f0: 0x00007fff9497d0f0 0x00007fff9497d118
0x7fff9497d100: 0x000000010050f270 0x0004e03100000007
(lldb) po 0x00007fff9497d0f0
NSObject
lldb调试:
-
p/x kevin获取对象在内存中的首地址- 打印首地址
po 0x00000001004b2180,我们得到了一个指向Person的指针地址0x1004b2180
- 打印首地址
-
x/4gx 0x00000001004b2180获取kevin的内存情况- 拿到kevin对象的isa
0x001d80010000848d
- 拿到kevin对象的isa
- 我们将kevin对象的类信息
isa(0x001d80010000848d) & 0x00007ffffffffff8ULL- 打印类信息的
首地址(0x0000000100008488) - 对首地址进行
po,我们得到Person,说明首地址指向的是内存中的Person,也印证了isa中的shiftcls中存放着类信息。
- 打印类信息的
- 我们再读取
Person类的内存情况(x/4gx 0x0000000100008488),- 对
Person的isa(0x0000000100008460)进行po,发现直接打印出了Person,这里大家不免就有疑惑了,0x0000000100008488和0x0000000100008460明明指向的不是同一片内存为啥会都打印出Person。继续往下分析...
- 对
- 我们既然拿到的
Person类的isa(0x0000000100008460),我们再对类进行类信息获取(p/x 0x0000000100008460 & 0x00007ffffffffff8ULL)并打印出首地址(0x0000000100008460),- 我们发现在
第4步得到的Person的类的isa(0x0000000100008460)与我们现在获取到的类信息地址是完全一致的。0x0000000100008488和0x0000000100008460指向的不是同一内存区域,却打印出了"同一结果"? - 这里引出元类
0x0000000100008488指向的是Person类,0x0000000100008460指向的是Person的元类,元类是底层源码自动生成,每个类都会对应一个元类。既然有了元类,我们继续摸元类的isa...
- 我们发现在
- 获取Person元类的内存情况
x/4gx 0x0000000100008460- 打印其首地址
po 0x00007fff9497d0f0,我们居然得到了NSObject, - 继续对
NSObject摸isa,发现无论是首地址还是其isa都是同一个地址0x00007fff9497d0f0。那这个NSObject是不是就是我们所熟悉的那个NSObject呢?留在后面验证...
isa走位图.png
通过上面的分析,是不是就印证了经典的isa走位图。按图上的isa走位我们知道了,最后isa的走位一直指向0x00007fff9497d0f0其实就是根元类。那为什么会打印出NSObject呢?印证与系统的isa是不是同一个
- 打印其首地址
(lldb) p/x NSObject.class
(Class) $12 = 0x00007fff9497d118 NSObject
(lldb) x/4gx 0x00007fff9497d118
0x7fff9497d118: 0x00007fff9497d0f0 0x0000000000000000
0x7fff9497d128: 0x00000001004b23c0 0x0001801000000003
(lldb) po 0x00007fff9497d118
NSObject
(lldb) po 0x00007fff9497d0f0
NSObject
(lldb) p/x 0x00007fff9497d0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00007fff9497d0f0
分析
- 获取
NSObject在内存中的首地址p/x NSObject.class得到0x00007fff9497d118 - 获取
NSObject的内存分布情况x/4gx 0x00007fff9497d118- 得到
isa(0x00007fff9497d0f0),此时的isa和首地址po出来都是NSObject,结合上面的分析,0x00007fff9497d0f0其实是NSObject的元类,并且内存地址和上面的内存地址是一致的,继续摸isa也是同一个地址。由于NSObject是根类,所以此时的0x00007fff9497d0f0是根元类的地址,印证了类在内存中只有一份且再次印证上图。
- 得到
继承关系走位图

结合两幅图,就得到了

现在我们明白了对象,类,元类,根元类的关系,那么为什么无论是对象,还是类都有isa呢?
objc_class & objc_object
- 通过
clang- 我们发现在底层提供的获取
类,父类,及元类的runtime方法,返回值都是由struct objc_class定义的结构体 -
Class也是由struct objc_class定义的。
- 我们发现在底层提供的获取
__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa __attribute__((deprecated));
} __attribute__((unavailable));
我们由此,猜想是不是所有的Class都是以objc_class为模板来创建的。
在objc4-781源码中搜索objc_class
- 在
runtime.h文件中发现
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;
/* Use `Class` instead of `struct objc_class *` */
这里我们可以看到,在OBJC2已经废弃了,从注释我们也可以看出现在用Class代替了struct objc_class *,是不是这里也可以说明Class都是以struct objc_class为模板创建的。继续往下找。
- 在
objc-runtime-new.h,发现了新版的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() const {
return bits.data();
}
....//省略部分代码
}
- 在
objc-runtime-old.h,发现了老版的objc_class定义
struct objc_class : objc_object {
Class superclass;
const char *name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
struct old_ivar_list *ivars;
struct old_method_list **methodLists;
Cache cache;
struct old_protocol_list *protocols;
// CLS_EXT only
const uint8_t *ivar_layout;
struct old_class_ext *ext;
...//省略部分代码
}
这里是不是大家就非常熟悉的看到了objc_class的继承关系,原来无论在老版还是新版objc_class都继承自objc_object
- 我们再全局搜索一下
objc_object- 在objc.h文件中
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
objc_object是一个结构体,其中包含了一个isa
- 在
objc-private.h中
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
...
}
我们看到objc_object其中包含一个isa,objc_class又是继承于objc_object。
结论
- 结构体类型
objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性 - 所有以
objc_class为模板创建的类都会有isa - 所有以
objc_object为模板创建的对象都会有isa
objc_class,objc_object关系图.png
类结构分析
我们在上面的探索中得出了类都是以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() const {
return bits.data();
}
....//省略部分代码
}
-
ISA:isa,8字节 -
superclass:指向父类的指针,8字节 -
cache:暂时无法确定大小,接下来一起探索 -
bits:探究的目标,通过内存偏移可以获取到,但需要确定cache的大小
cache的内存大小
进入cache_t类型的内部
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
这里只贴出影响大小的属性,一些staic修饰的属性,不占大小,故不做大小分析
-
计算第一部分,由if,elif修饰,所以只会存在一份
- CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
-
_buckets类型是struct bucket_t *,是个结构体指针类型,8字节 -
_mask类型是mask_t,是uint32_t类型,4个字节
-
- CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
-
_maskAndBuckets类型是uintptr_t,是个指针类型,8个字节 -
_mask_unused类型是mask_t,是uint32_t类型,4个字节
-
- CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
_flags类型是unsigned short,2个字节_occupied类型是unsigned short,2个字节
结论
cache总共占16个字节
类bits探索
- 通过类的首地址偏移
32个字节,打印Person类bits的内存地址
(lldb) p/x Person.class
(Class) $0 = 0x00000001000080e8 Person
(lldb) p/x 0x00000001000080e8 + 32
(long) $1 = 0x0000000100008108
(lldb) p (class_data_bits_t *)0x0000000100008108
(class_data_bits_t *) $2 = 0x0000000100008108
这里我们知道首地址偏移32位是
class_data_bits_t类型的,所有直接p此时的内存地址并强转为class_data_bits_t类型,没有报错,进一步论证此时的内存地址就是存储着bits
- 获取
bits的data()
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001011890e0
通过$2->data()去获取class_rw_t,这里可以从两个地方论证
- 在
objc_class的结构中,我们可以清晰的看到bits.data()
objc_class结构.png - 进入
class_data_bits_t的内部我们也能发现,class_data_bits内部有data()的get方法,还有set方法
class_data_bits内部有data()的get方法,还有set方法.png
-
data()是一个class_rw_t类型,再探索一下data(),进入class_rw_t内部
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 *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
methods():方法列表
- 获取方法列表
在获取到bits的data()的基础上
- 获取data()数据信息
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000208
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
- 获取方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000080d8
arrayAndFlag = 4295000280
}
}
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000080d8
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003e10 (KCObjc`-[Person sayHello])
}
}
}
- 对方法列表进行单个输出
(lldb) p $6.get(0)
(method_t) $7 = {
name = "sayHello"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003e10 (KCObjc`-[Person sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = "bobby"
types = 0x0000000100003f95 "@16@0:8"
imp = 0x0000000100003e20 (KCObjc`-[Person bobby])
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "setBobby:"
types = 0x0000000100003f9d "v24@0:8@16"
imp = 0x0000000100003e40 (KCObjc`-[Person setBobby:])
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = ".cxx_destruct"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003e70 (KCObjc`-[Person .cxx_destruct])
}
(lldb) p $6.get(4)
Assertion failed: (i < count), function get,
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
这里我们看到有4个方法,.cxx_destruct,sayHello,还有bobby的get和set方法,为啥没有我们的类方法sayNB呢?
properties():属性列表
探索方法,同上,这里我们也会发现,没有成员边量,只有我们的属性。
成员变量的存储
刚刚我们在properties()中未发现成员变量bobby,那个成员变量存在哪里呢?
通过探索,我们在class_rw_t的结构中发现了一个ro(),ro()的类型是``,进入其内部
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
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;
取到ro()中的ivars,然后就可以看到我们的成员变量,和属性都在里面
(lldb) p $16.ivars
(const ivar_list_t *const) $19 = 0x0000000100008140
(lldb) p *$19
(const ivar_list_t) $20 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000081a8
name = 0x0000000100003f4e "name"
type = 0x0000000100003f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
类方法的存储
我们在Person类的方法列表中,没有找到类方法。我们在元类的方法列表中发现了Person的类方法,由此我们可知,类的实例方法是存在类的bits里面。类的类方法是存放在元类的bits里面



