isa分析到元类
创建类LGPerson
,初始化实例p,LGPerson *p = [LGPerson alloc];
添加断点通过lldb动态调试探索isa的指向关系
<!-- main.m文件 -- >
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
}
return 0;
}
<!-- lldb调试信息 -->
(lldb) x p
0x10078ec80: 65 83 00 00 01 80 1d 01 00 00 00 00 00 00 00 00 e...............
0x10078ec90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
// 格式化打印p实例对象的内存地址
(lldb) p/x p
(LGPerson *) $1 = 0x000000010078ec80
// 格式化打印0x000000010078ec80地址下的连续地址空间内存储的数据,拿到了p对象的首地址,也就是isa指针地址
(lldb) x/4gx 0x000000010078ec80
0x10078ec80: 0x011d800100008365 0x0000000000000000
0x10078ec90: 0x0000000000000000 0x0000000000000000
// 通过isa指针和ISA_MASK的与操作,解析了LGPerson类对象
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $2 = 0x0000000100008360
// 打印这个地址数据,得到了LGPerson
(lldb) po 0x0000000100008360
LGPerson
// 通过实例对象的isa指向类对象,我拿到了类对象内存地址0x0000000100008360,格式化输出类对象的内存地址
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff88994008
0x100008370: 0x00007fff2021baf0 0x0000802c00000000
// 将类对象的首地址(isa指针地址)0x0000000100008338和ISA_MASK做与操作,然后打印得到LGPerson
(lldb) po 0x0000000100008338 & 0x00007ffffffffff8
LGPerson
0x0000000100008360
和0x0000000100008338
明显是两个内存地址,但是输出的却是一个对象,这是为什么呢?猜想类和我们的对象一样可以无限开辟,也就是内存中不只有一个类?下面进行验证
分析类对象在内存中的个数
void lgTestClassNum(void){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
lgTestClassNum();
}
return 0;
}
// 打印结果
0x100008360-
0x100008360-
0x100008360-
0x100008360
得出结论:
通过打印发现只有0x0000000100008360
才是类对象
即class
。而0x0000000100008338
并不是类,那它是什么呢?
打开Products
文件,把上面工程的可执行文件拖入MachOView
中进行查看
发现了__OBJC_METACLASS_RO_
,它就是元类对象
即MetaClass
,是由系统帮我们生成的。至此我们明白了0x0000000100008338
就是元类对象
isa走位图和继承链
LGPerson *p = [LGPerson alloc];
添加断点通过lldb动态调试
探索isa的指向关系
<!-- main.m文件 -- >
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
}
return 0;
}
<!-- lldb调试信息 -- >
(lldb) x/4gx p
0x100536870: 0x011d800100008365 0x0000000000000000
0x100536880: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff88994008
0x100008370: 0x00007fff2021baf0 0x0000802c00000000
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $3 = 0x0000000100008338
(lldb) po 0x0000000100008338
LGPerson
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff88993fe0 0x00007fff88993fe0
0x100008348: 0x0000000100536890 0x0002e03500000003
(lldb) p/x 0x00007fff88993fe0 & 0x00007ffffffffff8
(long) $5 = 0x00007fff88993fe0
(lldb) po 0x00007fff88993fe0
NSObject
(lldb) x/4gx 0x00007fff88993fe0
0x7fff88993fe0: 0x00007fff88993fe0 0x00007fff88994008
0x7fff88993ff0: 0x00000001030147b0 0x0003e03100000007
(lldb) p/x 0x00007fff88993fe0 & 0x00007ffffffffff8
(long) $5 = 0x00007fff88993fe0
(lldb) po 0x00007fff88993fe0
NSObject
得出结论
通过上面打印结果得出isa走位图,对象p isa -> 类LGPerson isa -> 元类MetaClass isa -> 根元类NSObject isa -> 根元类NSObject
- 对象的isa指向类Class
- 类对象的isa指向元类MetaClass
- 元类对象的isa指向根元类(rootMetaClass)
- 根元类的isa指针指向自己
下面探索继承链
void lgTestNSObject(void){
// 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(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
// LGPerson元类
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
lgTestNSObject();
}
return 0;
}
// 打印结果
0x10066c440 实例对象
0x7fff88994008 类
0x7fff88993fe0 元类
0x7fff88993fe0 根元类
0x7fff88993fe0 根根元类
2021-07-13 21:16:21.309877+0800 002-isa分析[25200:2810935] NSObject - 0x7fff88993fe0
2021-07-13 21:16:21.309940+0800 002-isa分析[25200:2810935] LGPerson - 0x100008338
2021-07-13 21:16:21.309986+0800 002-isa分析[25200:2810935] (null) - 0x0
2021-07-13 21:16:21.310028+0800 002-isa分析[25200:2810935] NSObject - 0x7fff88994008
得出结论
- NSOject对象的元类与根元类是一个
- 元类间也存在着继承的关系,跟类是一样的
- NSObject的父类是(null),地址为0x0,即NSObject没有父类
源码分析类的结构
上一篇探索对象本质中,提到两个结构体类型:objc_class
,objc_object
,在objc4源码
中搜索objc_class
的定义,源码中对其定义有两个版本
- 旧版位于
runtime.h
中,已经被废除
#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-runtime-new.h
这个是objc4-818
最新优化的,我们后面类的结构分析也是基于新版来分析
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 {
#if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
if (superclass == Nil)
return Nil;
#if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL
void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
if ((void *)superclass == stripped) {
void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
if ((void *)superclass != resigned)
return Nil;
}
#endif
void *result = ptrauth_auth_data((void *)superclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
return (Class)result;
...
通过上述的源码查看有以下几点说明:
- 结构体类型
objc_class
继承自objc_object
类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class
也拥有了isa属性
Class ISA:8字节
,CLass superclass:8字节
,cache_t cache:16字节
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
//省略跟内存大小无关的代码
}
- 探索ISA、superclass、cache是为了通过内存偏移找到bits,
class_data_bits_t bits:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
//此处省略了部分代码
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
//此处省略了部分代码
- 通过获取class_rw_t* 类型的data(),将会拿到这个类的methods、properties、protocols、deepCopy、ro等等信息
class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
//省略了部分代码
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
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这个结构体可以拿到类的信息,比如这个类的
methods、properties、protocols、deepCopy、ro
等等信息
指针和内存平移
普通指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10; //
int b = 10; //
LGNSLog(@"%d -- %p",a,&a);
LGNSLog(@"%d -- %p",b,&b);
}
return 0;
}
// 打印结果
KC打印: 10 -- 0x7ffeefbff33c
KC打印: 10 -- 0x7ffeefbff338
得出结论
- a、b都指向10,但是a、b的地址不一样,这是一种拷贝,属于
值拷贝
,也称为深拷贝
- a,b的地址之间相差 4 个字节,这取决于
a、b的类型
对象指针
// 对象 -
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
LGNSLog(@"%@ -- %p",p1,&p1);
LGNSLog(@"%@ -- %p",p2,&p2);
// 打印结果
KC打印: <LGPerson: 0x101855fe0> -- 0x7ffeefbff330
KC打印: <LGPerson: 0x101856030> -- 0x7ffeefbff328
得出结论
- p1、p2 是指针,p1 是 指向
[LGPerson alloc]
创建的空间地址,即内存地址
,p2 同理 -
&p1、&p2
是 指向 p1、p2对象指针的地址,这个指针就是二级指针
数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
int value = *(d+i);
NSLog(@"%d",value);
}
// 打印结果
2021-07-14 19:43:11.814671+0800 002-内存偏移[28063:3135116] 0x7ffeefbff350 - 0x7ffeefbff350 - 0x7ffeefbff354
2021-07-14 19:43:11.815728+0800 002-内存偏移[28063:3135116] 0x7ffeefbff350 - 0x7ffeefbff354 - 0x7ffeefbff358
2021-07-14 19:47:38.555196+0800 002-内存偏移[28063:3135116] 1
2021-07-14 19:47:38.555581+0800 002-内存偏移[28063:3135116] 2
2021-07-14 19:47:38.555991+0800 002-内存偏移[28063:3135116] 3
2021-07-14 19:47:38.556058+0800 002-内存偏移[28063:3135116] 4
得出结论
-
&c 和 &c[0]
都是取 首地址,即数组名等于首地址 -
&c 与 &c[1]
相差4个字节,地址之间相差的字节数,主要取决于存储的数据类型
- 可以通过
首地址+偏移量
取出数组中的其他元素,其中偏移量是数组的下标
,内存中首地址实际移动的字节数 等于 偏移量 * 数据类型字节数
类的内存结构内存计算
探索类的内存结构
- 打开
objc4源码
,LGPerson *p1 = [[LGPerson alloc] init];
添加断点,运行工程执行至断点处
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import "LGTeacher.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p1 = [[LGPerson alloc] init];
}
return 0;
}
根据前文提及的objc_class 的新版定义(objc4-818版本)有以下几个属性
struct objc_class : objc_object {
// Class ISA; //8字节
Class superclass; //Class 类型 8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略,未贴出
}
// lldb调试
// 0x00000001000083a8 对应isa,0x000000010036a140对应superclass,下面打印成功验证
// 0x000000010076ea40 对应cache,0x0002802800000003对应bits
(lldb) x/4gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x000000010076ea40 0x0002802800000003
(lldb) po 0x000000010036a140
NSObject
// 成功验证LGPerson 的父类superclass 为NSObject
(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
-
isa
属性:继承自objc_object
的isa,占8字节
-
superclass
属性:Class类型,Class是由objc_object
定义的,是一个指针
,占8字节
-
cache
属性:简单从类型class_data_bits_t
目前无法得知,而class_data_bits_t是一个结构体类型,结构体的内存大小需要根据内部的属性来确定,而结构体指针才是8字节 -
bits
属性:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits
计算 cache 类的内存大小
进入cache类cache_t
的定义(只贴出了结构体中非static修饰的属性,主要是因为static类型的属性 不存在结构体的内存中,另外方法函数也不在结构体内存中)有如下几个属性
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //是指针,占8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 占4字节
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
-
_bucketsAndMaybeMask
是uintptr_t类型,指针占8字节 -
_maybeMask
是mask_t类型, typedef uint32_t mask_t,uint32_t占4字节 -
_flags
是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节 -
_occupied
是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
最后计算出cache类的内存大小 = 8 + 4 + 2 + 2 = 16字节
总结:Class ISA:8字节,CLass superclass:8字节,cache_t cache:16字节
获取bits
// 继续上面lldb调试,获取bits
(lldb) x/4gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x000000010076ea40 0x0002802800000003
(lldb) po 0x000000010036a140
NSObject
// 成功验证LGPerson 的父类superclass 为NSObject
(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
(lldb) po sizeof(LGPerson.class)
8
// LGPerson的内存首地址 添加 isa superclass cache 共32字节内存偏移
// 将首地址平移32字节,获取bits地址
(lldb) p/x 0x100008380+0x20
(long) $4 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)0x00000001000083a0
(class_data_bits_t *) $5 = 0x00000001000083a0
// 通过bits地址获取bits数据
(lldb) p $5->data()
(class_rw_t *) $6 = 0x000000010076ea00
// 打印bits中的数据信息
(lldb) p *$6
(class_rw_t) $7 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
获取类的首地址有两种方式
- 通过
p/x LGPerson.class
直接获取首地址 - 通过
x/4gx LGPerson.class
打印内存信息获取
其中的data()
获取数据,是由objc_class
提供的方法
class_rw_t *data() const {
return bits.data();
}
- 从$6指针的打印结果中可以看出bits中存储的信息,其类型是
class_rw_t
,也是一个结构体类型
。但我们还是没有看到属性列表、方法列表
等,需要继续往下探索
探索 属性列表,即property_list
通过查看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 *>(&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};
}
}
在获取bits并打印bits信息的基础上,通过class_rw_
t提供的方法,继续探索 bits中的属性列表,以下是lldb 探索的过程图示
(lldb) p $5->data()
(class_rw_t *) $6 = 0x000000010076ea00
(lldb) p *$6
(class_rw_t) $7 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $7.properties()
(const property_array_t) $8 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008260
}
arrayAndFlag = 4295000672
}
}
}
(lldb) p $8.list
(const RawPtr<property_list_t>) $9 = {
ptr = 0x0000000100008260
}
(lldb) p $9.ptr
(property_list_t *const) $10 = 0x0000000100008260
(lldb) p *$10
(property_list_t) $11 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $11.get(0)
(property_t) $12 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
-
p $7.properties()
命令中的propertoes
方法是由class_rw_t
提供的,方法中返回的实际类型为property_array_t
- 由于list的类型是
property_list_t
,是一个指针,所以通过p *$10
获取内存中的信息,同时也证明bits中存储了property_list
,即属性列表
-
p $11.get(0)
,想要获取LGPerson中的成员变量name
遗留问题的探索
问题一 探索成员变量
的存储?
- 属性与成员变量的区别就是有没有
set、get
方法,如果有,则是属性
,如果没有,则是成员变量
。 - 由上面的属性列表分析可得出
property_list
中只有属性,没有成员变量,那么问题来了,成员变量存储在哪里?为什么会有这种情况? - 通过查看
objc_class
中bits
属性中存储数据的类class_rw_t
的定义发现,除了methods、properties、protocols
方法,还有一个ro方法,其返回类型是class_ro_t
,通过查看其定义,发现其中有一个ivars属性
,我们可以做如下猜测:是否成员变量就存储在这个ivar_list_t类型的ivars属性中呢?
通过lldb调试可以看出,获取的ivars属性,其中的count 为2,通过打印发现 成员列表中除了有hobby,还有name
,所以可以得出以下一些结论:
- 通过{}定义的
成员变量
,会存储在类的bits属性中,通过bits --> data() -->ro() --> ivars
获取成员变量列表,除了包括成员变量
,还包括属性定义的成员变量 - 通过
@property
定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list
获取属性列表,其中只包含属性
问题二 探索类方法
的存储?
- lldb调试可得出
methods list
中只有实例方法
,没有类方法
,那么问题来了,类方法存储在哪里?为什么会有这种情况?下面我们来仔细分析下 - 前面我们曾提及了元类,类对象的isa指向就是
元类
,元类是用来存储类的相关信息
,所以我们猜测:是否类方法存储在元类的bits
中呢?可以通过lldb命令来验证我们的猜测。 - 通过lldb对元类方法列表的打印结果,我们可以知道,我们的猜测是正确的,可以得出以下结论:
类的实例方法存储在
类的bits属性中
,通过bits --> methods() --> list
获取实例方法列表,例如LGPerson类的实例方法sayHello 就存储在 LGPerson类的bits属性中,类中的方法列表除了包括实例方法
,还包括属性的set方法
和get方法
类的类方法存储在
元类的bits属性
中,通过元类bits --> methods() --> list
获取类方法列表,例如LGPerson中的类方法sayBye
就存储在LGPerson类的元类(名称也是LGPerson)的bits属性中