前言
我们在前面的文章中主要对对象
进行了一些分析,那么类
是什么呢?接下来我们去探究分析。
一、isa走位和类的继承链
1. isa走位图
元类
在对象的本质这篇文章中,
我们提到根据isa
和掩码(ISA_MASK)
拿class
信息:
(lldb) x/4gx p1
0x1011422c0: 0x011d8001000081d1 0x0000000000000000
0x1011422d0: 0x697263534b575b2d 0x67617373654d7470
(lldb) po 0x011d8001000081d1 & 0x00007ffffffffff8ULL
WSPerson
(lldb) p/x 0x011d8001000081d1 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000081d0
这里我们拿到了class
信息了,我们继续用x/4gx
查看
(lldb) x/4gx 0x00000001000081d0
0x1000081d0: 0x00000001000081a8 0x0000000100357140
0x1000081e0: 0x000000010034f360 0x0000801000000000
这是什么?怎么还有值?如果跟上面一样,也用掩码(ISA_MASK)
位与下呢?来试一下看看:
(lldb) po 0x00000001000081a8 & 0x00007ffffffffff8ULL
WSPerson
(lldb) p/x 0x00000001000081a8 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00000001000081a8
啊这?0x00000001000081d0
和0x00000001000081a8
怎么都是WSPerson
,
难道类也和对象一样可以无限开辟内存?内存中有多个类?带着猜想我们去验证下:
WSPerson *c1 = WSPerson.class;
WSPerson *c2 = [WSPerson alloc].class;
WSPerson *c3 = object_getClass([WSPerson alloc]);
WSPerson *c4 = [WSPerson alloc].class;
NSLog(@"\nc1: %p\nc2: %p\nc3: %p\nc4: %p", c1, c2, c3, c4);
// 得到:
/*
c1: 0x1000081d0
c2: 0x1000081d0
c3: 0x1000081d0
c4: 0x1000081d0
*/
说明0x00000001000081d0
是类,而0x00000001000081a8
不是,那么它是什么呢,接下来我们借助MachOView
来查看:
- 在过程的
Products
目录拿到可执行文件(黑色的) - 然后用
MachOView
打开
-
首先查看
Section64(_DATA,__objc_classrefs)
,这里面记录了引用类的地址:- 可以确定
0x00000001000081d0
才是WSPerson
类,并且只有一个。
- 可以确定
-
然后我们找到
Symbol table
(符号表)中的Symbols
,然后搜索关键字class
,- 首先
_OBJC_CLASS
是objc_class
,这个我们知道,但_OBJC_METACLASS
是个什么?我们没有创建这个啊? _OBJC_METACLASS
叫元类
,是系统帮忙创建的。
- 首先
isa走位
- 在上述步骤中,我们拿到了
(元类)
,我们接下来继续查找元类
的isa
指向:
(lldb) x/4gx 0x00000001000081a8
0x1000081a8: 0x00000001003570f0 0x00000001003570f0
0x1000081b8: 0x0000000101042d90 0x0001e03100000007
(lldb) p/x 0x00000001003570f0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00000001003570f0
(lldb) po 0x00000001003570f0
NSObject
这里我们得到了NSObject
,也就是根元类
,说明元类
,我们查找对根元类
的isa
指向:
(lldb) x/4gx 0x00000001003570f0
0x1003570f0: 0x00000001003570f0 0x0000000100357140
0x100357100: 0x00000001010434d0 0x0004e03100000007
(lldb) p/x 0x00000001003570f0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00000001003570f0
发现还是自己,说明 根元类的isa
指向自己 。
- 我们再来看看
NSObject
的isa
指向:
(lldb) p/x NSObject.class
(Class) $10 = 0x0000000100357140 NSObject
(lldb) x/4gx 0x0000000100357140
0x100357140: 0x00000001003570f0 0x0000000000000000
0x100357150: 0x000000010114a0a0 0x0002801000000003
(lldb) p/x 0x00000001003570f0 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x00000001003570f0
这里我们发现NSObject(根类)
的isa
指向根元类
,然后根元类
的isa
指向自己。
- 于是我们得到
isa
走位图:
2. 继承链
我们知道类都有继承关系,那么Meta(根元类)
呢,我们来探索下:
- 首先我们来打印下
NSObject
的相关信息:
NSObject *obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class rootClass = object_getClass(objClass);
Class metaClass = object_getClass(rootClass);
Class metaMetaClass = object_getClass(metaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类", obj, objClass, rootClass, metaClass, metaMetaClass);
// 打印结果
/*
0x100607a20 实例对象
0x7fff8fafe118 类
0x7fff8fafe0f0 元类
0x7fff8fafe0f0 根元类
0x7fff8fafe0f0 根根元类
*/
- 然后再打印
WSPerson
的元类,即元类父类信息:
Class pMetaClass = object_getClass(WSPerson.class);
Class superPMetaClass = class_getSuperclass(pMetaClass);
NSLog(@"\n%p WSPerson元类\nWSPerson元类的父类: %@ --- %p", pMetaClass, superPMetaClass, superPMetaClass);
// 打印结果
/*
0x100008218 WSPerson元类
WSPerson元类的父类: NSObject --- 0x7fff8fafe0f0
*/
从这里,我们能够看出来,WSPerson
元类的父类继承自NSObject
的根元类
疑问:难道任何类的
元类
都继承自根元类
?我们再来验证下:
// WSTeacher 继承 WSPerson 继承 NSObject
Class tMetaClass = object_getClass(WSTeacher.class);
Class tsuperPMetaClass = class_getSuperclass(tMetaClass);
NSLog(@"\n%p WSTeacher元类\nWSTeacher元类的父类: %@ --- %p", tMetaClass, tsuperPMetaClass, tsuperPMetaClass);
// 打印结果
/*
0x100008268 WSTeacher元类
WSTeacher元类的父类: WSPerson --- 0x100008218
*/
这里可以看出 WSTeacher
元类的父类是继承自WSPerson元类
,说明猜想不对啊。
新的疑问:
1.NSObject
的父类又是什么呢?
2.NSObject
的根元类又继承自什么呢? 我们再来测试下:
// NSObject
Class nsSuperClass = class_getSuperclass(NSObject.class);
NSLog(@"\nNSObject的父类: %@ --- %p", nsSuperClass, nsSuperClass);
Class rootMetaSuperClass = class_getSuperclass(rootClass);
NSLog(@"\nNSObject的根元类: %@ --- %p", rootMetaSuperClass, rootMetaSuperClass);
// 打印结果
/*
NSObject的父类: (null) --- 0x0
NSObject的根元类: NSObject --- 0x7fff8fafe118
*/
得出结论:原来NSObject
的父类是null
,而NSObject的根元类
还是继承NSObject
。用图解来展示就是:
合并两个图
- 将上面得到的两个图合并,我们就得到了一个著名的流程图:
二、类的结构
- 我们再上节课看了对象的机构,那么类的结构是怎样的呢?我们去打印分析下:
(lldb) x/4gx WSPerson.class
0x100008240: 0x0000000100008218 0x00007fff8fafe118
0x100008250: 0x0000000100498290 0x0002801c00000003
从打印结果,发现类
里面都有值,那这些是什么呢,我们在objc4-818.2
的源码中去查看:
- 我们知道
Class
继承objc_class
,然后我们在源码中去搜索,发现有两个:
struct objc_class // OBJC2_UNAVAILABLE
// 和
struct objc_class : objc_object
由于第一个是OBJC2_UNAVAILABLE
也就是objc2不可用
,所以我们选择分析第二个,这里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
怎么会有个isa
呢,我们去objc_object
里查看:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
得知这个isa
是继承objc_object
的,那么objc_class
有4个成员变量
,第一个是Class isa
,第二个是superclass
,第三个是cache
,第四个是bits
,前三个我们能猜到一些,那bits
是个什么鬼?
- 在
bits
的注释处提到class_rw_t
,这个是什么呢,objc_class
中对它有一个存取:
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
它是从bits.data()
里获取的,我们再去看看class_rw_t
,我们看到了些熟悉的东西:
const method_array_t methods() // 方法列表
const property_array_t properties() // 属性列表
const protocol_array_t protocols() // 代理列表
也就是说这些内存数据都是存在bits
中的,我们用图解来表示:
那么我们怎么去拿这些数据呢?我们继续去探究。
三、获取内存数据
1. 内存偏移
- 在探究获取类的内存数据之前,我们先讲下内存偏移:
- 普通指针:
int a = 666;
int b = 666;
ysLog(@"\na: %d --- %p", a, &a);
ysLog(@"\nb: %d --- %p", b, &b);
// 打印结果
/*
a: 666 --- 0x7ffeef2c5c7c
b: 666 --- 0x7ffeef2c5c78
*/
这里a
和b
的指针都指向666
,也就是所谓的值copy
,如图所示:
2.对象指针:
WSPerson *p1 = [WSPerson alloc];
WSPerson *p2 = [WSPerson alloc];
ysLog(@"\np1: %@ --- %p", p1, &p1);
ysLog(@"\np2: %@ --- %p", p2, &p2);
// 打印结果
/*
p1: <WSPerson: 0x60000122d020> --- 0x7ffeef2c5c70
p2: <WSPerson: 0x60000122d040> --- 0x7ffeef2c5c68
*/
这里指针p1
和p2
指向两个对象,如图:
3. 数组指针
int c[4] = {2, 4, 6, 8};
int *d = c;
ysLog(@"\nc: %p -- %p -- %p", &c, &c[0], &c[1]);
ysLog(@"\nd: %p -- %p -- %p", d, d+1, d+2);
// 打印结果
/*
c: 0x7ffeef2c5c90 -- 0x7ffeef2c5c90 -- 0x7ffeef2c5c94
d: 0x7ffeef2c5c90 -- 0x7ffeef2c5c94 -- 0x7ffeef2c5c98
*/
首先c
的首地址
就是c第一个元素的地址
,所以&c
和&c[0]
打印结果是一样的。这里将c
赋值给指针*d
,为什么d+1
和&c[1]
是一样的呢,d+1
是什么?
我们用图解来解释下:
这里取内存时,是由首地址平移一个单位
,这里一个单位是int
类型,也就是4字节
。也可以写成d+1
,这里的1
就是一个单位。所以读取c数组
元素,也可以这样写:
for (int i = 0; i < 4; i++) {
int e = *(d + i);
ysLog(@"%d", e);
}
// 打印结果
/*
2
4
6
8
*/
由此我们可以推测:类的首地址可以平移一些大小,拿到一些内容。接下来我们去验证。
2. 计算平移大小
上面我们了解了内存平移
,接下来我们用内存平移
来获取bits
数据。在bits
之前,有isa8字节
,superclass
也是8字节
(因为superclass
是Class
类型,而Class
是一个指针类型,所以是8字节
),还有个cache
,它是cache_t
类型,cache_t
是一个结构体,里面有很多内容:
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
类型,这部分在全局区
,也不占用不占结构体内存
,所以就计算union
和_bucketsAndMaybeMask
这个类型就好了。
- 我们先来计算
_bucketsAndMaybeMask
,它是个泛型
:
struct explicit_atomic : public std::atomic<T>
// T是要传入的类型
它的类型是根据传入的类型来确定,然后我们继续看传入的uintptr_t
类型,
typedef unsigned long uintptr_t;
uintptr_t
类型是 unsigned long
类型,所以占8字节。
- 我们在看
union
中的结构,先看_maybeMask
,这个也是根据传入的类型来定,传入的是mask_t
,是uint32_t
类型,所以是4字节
typedef uint32_t mask_t;
再看 _flags
和_occupied
,他们都是uint16_t
类型,所以都是2字节
。根据联合体的特性,我们得出其大小为8字节
。
- 结论:
cache_t
内存大小为16字节
,至此我们拿到了偏移大小为8+8+16 = 32字节
,接下来我们去拿bits
中的内容。
3. 拿bits
中的内容
- 我们先拿到
WSPerson
的首地址,然后平移32字节
,在16进制
中就是20
:
(lldb) x/4gx WSPerson.class
0x100008270: 0x0000000100008248 0x0000000100357140
0x100008280: 0x000000010034f380 0x0000801800000000
(lldb) p/x 0x100008270+0x20
(long) $1 = 0x0000000100008290
这里我们拿到了bits
,因为bits
是class_data_bits_t
类型,所以我们可以打印下它的信息:
(lldb) p (class_data_bits_t *)0x0000000100008290
(class_data_bits_t *) $2 = 0x0000000100008290
这样就拿到了bits
信息,接下来要做什么呢?我们看到源码中有这样一个操作,
class_rw_t *data() const {
return bits.data();
}
这里有个bits.data()
的操作,我们也来操作下看看:
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010107e550
因为
$2
是一个指针,所以调用方法时用->
,如果是结构体
就用.
这样我们就拿到了class_rw_t
指针,然后我们查看下指针的内容:
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
我们的WSPerson
类中有个name
的属性,但在哪呢?我们在class_rw_t
中去找下,在上面分析类结构时,我们看到了properties
,它是一个property_array_t
数组类型:
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};
}
}
我们再去根据拿到的class_rw_t
结构体去拿properties()
得到:
(lldb) p $4.properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000081c0
}
arrayAndFlag = 4295000512
}
}
}
这样我们就得到了数组信息,property_array_t
是什么呢,我们在源码中查看下:
class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
property_array_t
继承list_array_tt
,我们再来看下list_array_tt
:
class list_array_tt {
struct array_t {
};
protected:
// iterator是 迭代器方法
class iterator {
const Ptr<List> *lists;
const Ptr<List> *listsEnd;
typename List::iterator m, mEnd;
public:
iterator(const Ptr<List> *begin, const Ptr<List> *end)
: lists(begin), listsEnd(end)
{
if (begin != end) { // 从begin和end的位置都放到里面,意味着具备遍历能力
m = (*begin)->begin();
mEnd = (*begin)->end();
}
}
// 取出元素
const Element& operator * () const {
return *m;
}
Element& operator * () {
return *m;
}
...
}
...
}
我们接着上面的步骤继续往里面拿数据:
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
ptr = 0x00000001000081c0
}
这个$6
是上面上述数组元素遍历出的一个迭代器(iterator)
,我们打印下他的ptr
:
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x00000001000081c0
这里我们再来查看下$7
的内容:
(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 1)
}
- 这里得出
$8
是一个property_list_t(数组)
类型,我们再查看它的源码:
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
// property_list_t 继承 entsize_list_tt
struct entsize_list_tt {
...
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
...
}
根据源码可知我们可以使用get
来拿内容,因为我们的类只有一个属性,所以可以使用get(0)
:
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
这里我们就拿到了name属性
。整体的流程如下:
4. 获取方法
我们拿到了类的属性后,这里有一个新猜想,我们用类似的方法可以拿到方法
和ivar(实例变量)
吗?我们去尝试下,首先在WSPerson
里添加一些方法,属性和实例变量:
@interface WSPerson : NSObject {
NSString *subject;
}
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
- (void)sayNB;
+ (void)sayGood;
@end
然后们来获取方法:
(lldb) x/4gx WSPerson.class //第一步
0x100008338: 0x0000000100008310 0x0000000100357140
0x100008348: 0x000000010034f380 0x0000802800000000
(lldb) p (class_data_bits_t *)0x100008358 //第二步 获取bits地址
(class_data_bits_t *) $1 = 0x0000000100008358
(lldb) p $1->data() //第三步 获取data数据
(class_rw_t *) $2 = 0x000000010066d780
(lldb) p $2.methods() //第四步 获取 class_rw_t 中的 方法数组
(const method_array_t) $3 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008180
}
arrayAndFlag = 4295000448
}
}
}
Fix-it applied, fixed expression was:
$2->methods()
(lldb) p $3.list //第五步 获取方法列表的指针数组
(const method_list_t_authed_ptr<method_list_t>) $4 = {
ptr = 0x0000000100008180
}
(lldb) p $4.ptr //第六步 获取ptr
(method_list_t *const) $5 = 0x0000000100008180
(lldb) p *$5 //第七步 获取内存信息
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 5)
}
(lldb) p $6.get(0)
(method_t) $7 = {}
(lldb) p $6.get(1)
(method_t) $8 = {}
(lldb) p $6.get(2)
(method_t) $9 = {}
(lldb) p $6.get(3)
(method_t) $10 = {}
(lldb) p $6.get(4)
(method_t) $11 = {}
(lldb)
前面的步骤和拿属性时大致相同,但怎么最后拿方法时拿不到呢?我们观察这个打印结果,发现有个method_t
类型的数据,而拿属性时,属性的类型为property_t
类型:
struct property_t {
const char *name;
const char *attributes;
};
查阅源码得知结构体property_t
只有两个成员,所以能直接打印出结果,我们再看看method_t
,发现一个big
结构体:
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
很明显
method_t
中的big
结构体中,才是我们需要的数据。
于是我们去验证:
(lldb) p $6.get(0).big
(method_t::big) $12 = {
name = "sayNB"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003cd0 (KCObjcBuild`-[WSPerson sayNB])
}
Fix-it applied, fixed expression was:
$6.get(0).big()
(lldb) p $6.get(1).big
(method_t::big) $13 = {
name = "name"
types = 0x0000000100003f95 "@16@0:8"
imp = 0x0000000100003ce0 (KCObjcBuild`-[WSPerson name])
}
Fix-it applied, fixed expression was:
$6.get(1).big()
(lldb) p $6.get(2).big
(method_t::big) $14 = {
name = "setName:"
types = 0x0000000100003f9d "v24@0:8@16"
imp = 0x0000000100003d00 (KCObjcBuild`-[WSPerson setName:])
}
Fix-it applied, fixed expression was:
$6.get(2).big()
(lldb) p $6.get(3).big
(method_t::big) $15 = {
name = "setNickName:"
types = 0x0000000100003f9d "v24@0:8@16"
imp = 0x0000000100003d50 (KCObjcBuild`-[WSPerson setNickName:])
}
Fix-it applied, fixed expression was:
$6.get(3).big()
(lldb) p $6.get(4).big
(method_t::big) $16 = {
name = "nickName"
types = 0x0000000100003f95 "@16@0:8"
imp = 0x0000000100003d30 (KCObjcBuild`-[WSPerson nickName])
}
Fix-it applied, fixed expression was:
$6.get(4).big()
我们验证了猜想。但出现个新问题:方法显示一共有5个,除去属性的setter
和getter
,只有一个对象方法
,实例对象和类方法哪儿去了?
5. 寻找实例变量和类方法
实例变量
既然在方法里没有实例变量
,那属性列表应该也没有,那会在哪?难道是在class_rw_t
中的其他类型?我们再去查看class_rw_t
,通过观察我们看到有个class_ro_t
类型的指针ro()
,这里我们看到有个ivar_list_t
:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__ // 表示指针长度为64位,即地址长度以64位长度来表示
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;
...
}
我猜测实例变量
可能就在这里,接下来我们去验证:
(lldb) x/4gx WSPerson.class
0x100008338: 0x0000000100008310 0x0000000100357140
0x100008348: 0x000000010034f380 0x0000802800000000
(lldb) p (class_data_bits_t *)0x100008358
(class_data_bits_t *) $1 = 0x0000000100008358
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010195f830
(lldb) p $2.ro()
(const class_ro_t *) $3 = 0x0000000100008138
Fix-it applied, fixed expression was:
$2->ro()
(lldb) p *$3
(const class_ro_t) $4 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "WSPerson" {
Value = 0x0000000100003f2e "WSPerson"
}
}
baseMethodList = 0x0000000100008180
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008200
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008268
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $4.ivars
(const ivar_list_t *const) $5 = 0x0000000100008200
(lldb) p *$5
(const ivar_list_t) $6 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $6.get(0)
(ivar_t) $7 = {
offset = 0x00000001000082a8
name = 0x0000000100003f3f "subject"
type = 0x0000000100003f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
根据前面的经验,我们顺利的拿到了实例变量 subject
。
类方法
前面我们用当前类去获取方法,并没有找到类方法
,那么会在哪呢?根据isa
的走位图,我们知道类的isa
指向元类
,那么元类里有没有呢?我们去验证下:
(lldb) p/x object_getClass(WSPerson.class) // 拿到元类
(Class) $0 = 0x0000000100008310 // 元类的bit地址
(lldb) p (class_data_bits_t *)0x0000000100008330
(class_data_bits_t *) $1 = 0x0000000100008330
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001022430a0
(lldb) p $2.methods()
(const method_array_t) $3 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008118
}
arrayAndFlag = 4295000344
}
}
}
Fix-it applied, fixed expression was:
$2->methods()
(lldb) p $3.list
(const method_list_t_authed_ptr<method_list_t>) $4 = {
ptr = 0x0000000100008118
}
(lldb) p $4.ptr
(method_list_t *const) $5 = 0x0000000100008118
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $6.get(0).big
(method_t::big) $7 = {
name = "sayGood"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003cc0 (KCObjcBuild`+[WSPerson sayGood])
}
Fix-it applied, fixed expression was:
$6.get(0).big()
我们先拿到元类,然后根据前面的方法拿method
,最后我们拿到了类方法 sayGood
。
总结
- 获取
属性
:我们拿到当前类
的bits.data
,它的类型是class_rw_t
,然后去找class_rw_t
的properties
数据,最后拿到property_list_t
数组,然后根据get()
方法,传入属性位置,进而拿到属性。 - 获取
实例方法
:我们拿到class_rw_t
数据后,然后去拿里面的methods()
,然后一步步往里面拿,拿到method_t
的数据类型,再调用里面的big
方法就拿到了实例方法
。 - 获取
实例变量
:我们拿到class_rw_t
后,然后拿到里面的ro
方法,在拿到ivars
信息,然后一步步拿到entsize_list_tt
数组,再根据get()
传入位置,就拿到了实例变量
。 - 获取
类方法
:我们先拿到元类
,然后根据总结2
的步骤,最后就拿到了类方法
。