忙不是不学习的借口
在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走位图.png
现在我们明白了对象,类,元类,根元类的关系,那么为什么无论是对象,还是类都有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里面
网友评论