iOS底层-类的探究分析(上)

427 阅读12分钟

前言

我们在前面的文章中主要对对象进行了一些分析,那么是什么呢?接下来我们去探究分析。

一、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 

啊这?0x00000001000081d00x00000001000081a8怎么都是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来查看:

  1. 在过程的Products目录拿到可执行文件(黑色的)
  2. 然后用MachOView打开
  • 首先查看Section64(_DATA,__objc_classrefs),这里面记录了引用类的地址:

    截屏2021-06-19 22.13.59.png

    • 可以确定0x00000001000081d0才是WSPerson类,并且只有一个。
  • 然后我们找到Symbol table(符号表)中的Symbols,然后搜索关键字class

    截屏2021-06-19 22.25.00.png

    • 首先_OBJC_CLASSobjc_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指向自己

  • 我们再来看看NSObjectisa指向:
(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走位图:

截屏2021-06-20 00.06.18.png

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。用图解来展示就是:

截屏2021-06-20 08.54.51.png

合并两个图

  • 将上面得到的两个图合并,我们就得到了一个著名的流程图:

isa流程图.png

二、类的结构

  • 我们再上节课看了对象的机构,那么类的结构是怎样的呢?我们去打印分析下:
(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_class4个成员变量,第一个是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中的,我们用图解来表示:

截屏2021-06-20 15.43.56.png 那么我们怎么去拿这些数据呢?我们继续去探究。

三、获取内存数据

1. 内存偏移

  • 在探究获取类的内存数据之前,我们先讲下内存偏移:
  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
*/

这里ab的指针都指向666,也就是所谓的值copy,如图所示:

截屏2021-06-20 11.00.20.png

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
*/

这里指针p1p2指向两个对象,如图:

截屏2021-06-20 11.00.29.png 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是什么? 我们用图解来解释下:

截屏2021-06-20 11.34.08.png
这里取内存时,是由首地址平移一个单位,这里一个单位是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字节(因为superclassClass类型,而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,因为bitsclass_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属性。整体的流程如下:

截屏2021-06-20 20.41.13.png

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个,除去属性的settergetter,只有一个对象方法实例对象和类方法哪儿去了?

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

总结

  1. 获取属性:我们拿到当前bits.data,它的类型是class_rw_t,然后去找class_rw_tproperties数据,最后拿到property_list_t数组,然后根据get()方法,传入属性位置,进而拿到属性。
  2. 获取实例方法:我们拿到class_rw_t数据后,然后去拿里面的methods(),然后一步步往里面拿,拿到method_t的数据类型,再调用里面的big方法就拿到了实例方法
  3. 获取实例变量:我们拿到class_rw_t后,然后拿到里面的ro方法,在拿到ivars信息,然后一步步拿到entsize_list_tt数组,再根据get()传入位置,就拿到了实例变量
  4. 获取类方法:我们先拿到元类,然后根据总结2的步骤,最后就拿到了类方法