isa和类的结构分析

一、isa到元类

先定义一个ApplePerson

@interface ApplePerson : NSObject
@end
复制代码

添加断点

image.png

x p打印一下对象指针地址

(lldb) x p
0x100553e50: 69 84 00 00 01 00 00 01 00 00 00 00 00 00 00 00  i...............
0x100553e60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
复制代码

发现地址是0x100553e50,在p/x一下指针地址

(lldb) p/x p
(ApplePerson *) $1 = 0x0000000100553e50
复制代码

发现0x0000000100553e50和上面0x100553e50完全一致,再x/4gx打印一下isa地址

(lldb) x/4gx p
0x100553e50: 0x0100000100008469 0x0000000000000000
0x100553e60: 0x0000000000000000 0x0000000000000000
复制代码

得到0x0100000100008469就是isa的地址,和arm64isa掩码ISA_MASK & 一下 得出类的地址

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
复制代码

相与

(lldb) p/x 0x0100000100008469 & 0x0000000ffffffff8
(long) $3 = 0x0000000100008468
复制代码

po 一下变量$3类的地址

(lldb) po 0x0000000100008468
ApplePerson
复制代码

得出ApplePerson,那如果对类地址再进行x/4gx

(lldb) x/4gx 0x0000000100008468
0x100008468: 0x0000000100008440 0x00000001eb780d08
0x100008478: 0x0000000180218e60 0x0000802900000000
复制代码

得到一个和上面类似的地址结构,如果假设0x0000000100008440是一个isa,对它和掩码&一下

(lldb) po 0x0000000100008440 & 0x0000000ffffffff8
ApplePerson
复制代码

也是一个ApplePerson
那对它再&一下

(lldb) p/x 0x0000000100008440 & 0x0000000ffffffff8
(long) $7 = 0x0000000100008440
复制代码

还是原来的地址,而0x00000001000084680x0000000100008440 po出来都是ApplePerson,我们猜想,内存是不是只开辟了不止一个类,接下里我们通过代码测试一下

//分析类对象在内存里存在的个数
void appleTestClassNum(void){
    Class cls1 = [ApplePerson class];
    Class cls2 = [ApplePerson alloc].class;
    Class cls3 = object_getClass([ApplePerson alloc]);
    Class cls4 = [ApplePerson alloc].class;
    NSLog(@"\n- %p \n- %p \n- %p \n- %p",cls1,cls2,cls3,cls4);
}
复制代码

打印输出

- 0x100008468 
- 0x100008468 
- 0x100008468 
- 0x100008468
复制代码

发现他们打印指向的地址都是一样的,都是0x100008468和上面的0x0000000100008468是一致的,是类的地址,而0x0000000100008440不是类的地址,所以它并不是类

分析:对象的isa指向类,而类的isa又指向这个新的结构

graph LR
对象 .- 对象的isa ====>  类  .- 类的isa ====> 新的结构

接下来,我们点开执行文件

image.png

把它拉进烂苹果里面

image.png

点开objc_classrefs一列,点开ObjC2 References,可以看到只有两个类ApplePersonAppleEmployee

image.png

点击符号表,查询class

image.png

可以看到一个_OBJC_CLASS_ApplePerson和一个_OBJC_METACLASS_ApplePerson,METACLASS就是元类,我们并没有创建过METACLASS,所以它是系统生成的

graph LR
对象 .- 对象的isa ====>  类  .- 类的isa ====> 元类

二、isa走位图和继承链

上面找到了元类,那元类前面是什么,接下来我们再跑一次项目,依旧是上面的代码,加打印地方断点

ApplePerson *p = [ApplePerson alloc];
NSLog(@"%@",p);
复制代码

x/4gx 一下拿到指针地址

(lldb) x/4gx p
0x100617ea0: 0x0100000100008299 0x0000000000000000
0x100617eb0: 0x0000000000000000 0x0000000000000000
复制代码

isa_mask & 一下拿到类地址

(lldb) p/x 0x0100000100008299 & 0x0000000ffffffff8
(long) $1 = 0x0000000100008298
复制代码

po 打印一下

(lldb) po 0x0000000100008298
ApplePerson
复制代码

对类地址进行x/4gx

(lldb) x/4gx 0x0000000100008298
0x100008298: 0x0000000100008270 0x00000001eb780d08
0x1000082a8: 0x0000000180218e60 0x0000802900000000
复制代码

继续和isa_mask & 一下拿到元类地址

(lldb) p/x 0x0000000100008270 & 0x0000000ffffffff8
(long) $3 = 0x0000000100008270
复制代码

po 打印一下

(lldb) po 0x0000000100008270
ApplePerson
复制代码

如果我们继续对元类进行x/4gx

(lldb) x/4gx 0x0000000100008270
0x100008270: 0x00000001eb780ce0 0x00000001eb780ce0
0x100008280: 0x00010001050376f0 0x0002e03500000000
复制代码

继续和isa_mask & 一下

(lldb) p/x 0x00000001eb780ce0 & 0x0000000ffffffff8
(long) $5 = 0x00000001eb780ce0
(lldb) 
复制代码

得到$5地址 0x00000001eb780ce0 和上面的元类地址一模一样

此时,我们po打印一下$5

(lldb) po 0x00000001eb780ce0
NSObject
(lldb) 
复制代码

NSObject,得到了根元类

graph LR
对象 .- 对象的isa ===>  类  .- 类的isa ===> 元类.- 元类的isa===>  根元类

简化一下

graph LR
对象的isa -->   类的isa -->  元类的isa-->  根元类

此时,我们再打印一下跟类NSObject类的地址

(lldb) p/x NSObject.class
(Class) $7 = 0x00000001eb780d08 NSObject
(lldb) 
复制代码

显然,$7 = 0x00000001eb780d08和 上面的0x00000001eb780ce0并不一致,我们对它再一次x/4gx

(lldb) x/4gx 0x00000001eb780d08
0x1eb780d08: 0x00000001eb780ce0 0x0000000000000000
0x1eb780d18: 0x0001000100612180 0x0001801000000000
复制代码

再拿到isa的地址再x/4gx

(lldb) x/4gx 0x00000001eb780ce0
0x1eb780ce0: 0x00000001eb780ce0 0x00000001eb780d08
0x1eb780cf0: 0x0001000100704080 0x0002e03400000000
复制代码

得到0x00000001eb780ce0和上面一致,再和isa_mask & 一下

(lldb) p/x 0x00000001eb780ce0 & 0x0000000ffffffff8
(long) $8 = 0x00000001eb780ce0
复制代码

依然,还是0x00000001eb780ce0

graph LR
根类 .- 根类的isa ===>  根元类.-> 根元类

所以,我们推测如下的类结构图:

graph LR
根类对象 ..-> 根类 ....-> 根元类
父类对象 ..-> 父类 ..-> 父类的元类
父类的元类..-> 根元类
子类对象 ..-> 子类 ..-> 子类的元类
子类的元类..-> 根元类
根元类.-> 根元类

接下来,我们通过代码验证元类之间的关系

void appleTestNSObject(void){
//   初始化NSObject根类实例对象
    NSObject *obj = [NSObject alloc];
//   拿到NSObject根类
    Class obj_class = object_getClass(obj);
//   拿到NSObject的根元类
    Class meta_obj_class = object_getClass(obj_class);
//   拿到NSObject的根根元类
    Class root_meta_obj_class = object_getClass(meta_obj_class);
//   拿到NSObject的根根根元类
    Class root_root_meta_obj_class = object_getClass(root_meta_obj_class);
    
    NSLog(@"\n %p 根类实例对象\n %p 根类\n %p 根元类\n %p 根根元类\n %p 根根根元类\n",obj,obj_class,meta_obj_class,root_meta_obj_class,root_root_meta_obj_class);
}
复制代码

打印一下

 0x10512b040 根类实例对象
 0x1eb780d08 根类
 0x1eb780ce0 根元类
 0x1eb780ce0 根根元类
 0x1eb780ce0 根根根元类
复制代码

可以发现NSObject的根元类以及往上走都是同一地址,指向了根元类自己

我们再测试一下子类对象AppleEmployee的元类指向

 // AppleEmployee的元类
    Class AppleEmployee_MetaClass = object_getClass(AppleEmployee.class);
    // AppleEmployee父类的元类
    Class AppleEmployee_SuperMetaClass = class_getSuperclass(AppleEmployee_MetaClass);
    //AppleEmployee根元类
    Class AppleEmployee_RootMetaClass = class_getSuperclass(AppleEmployee_SuperMetaClass);
    
    NSLog(@"子类的元类:  %@ - 子类元类的地址%p",AppleEmployee_MetaClass,AppleEmployee_MetaClass);
    NSLog(@"父类的元类:  %@   - 父类元类的地址%p",AppleEmployee_SuperMetaClass,AppleEmployee_SuperMetaClass);
    NSLog(@"子类的根元类: %@     - 子类根元类的地址%p",AppleEmployee_RootMetaClass,AppleEmployee_RootMetaClass);
复制代码

打印得出如下结果:

子类的元类:   AppleEmployee 	- 子类元类的地址0x1000082c0
父类的元类:   ApplePerson   	- 父类元类的地址0x100008270
子类的根元类: NSObject     	- 子类根元类的地址0x1eb780ce0
复制代码

我们可以看到,子类的元类指向父类的元类,父类的元类指向了根元类
所以,我们得出元类的继承链:

graph BT

子类的元类 --> 父类的元类 --> 根元类

而子类的继承链

    //AppleEmployee的父类
    Class AppleEmployee_SuperClass = class_getSuperclass(AppleEmployee.class);
    //AppleEmployee的根类
    Class AppleEmployee_rootClass = class_getSuperclass(AppleEmployee_SuperClass);
    //AppleEmployee的根根类
    Class AppleEmployee_rootrootClass = class_getSuperclass(AppleEmployee_rootClass);
    NSLog(@"\nAppleEmployee的父类 - %@\nAppleEmployee的根类 - %@\nAppleEmployee的根根类 - %@",AppleEmployee_SuperClass,AppleEmployee_rootClass,AppleEmployee_rootrootClass);
复制代码

打印一下

AppleEmployee的父类 - ApplePerson
AppleEmployee的根类 - NSObject
AppleEmployee的根根类 - (null)
复制代码

所以,得出子类的继承链

graph BT

子类 --> 父类 --> 根类 --> nil

我们再查找根元类的父类

//    根元类的父类
    Class rootMetaClass__superClass = class_getSuperclass(root_meta_obj_class);
    NSLog(@"%@ - %p", rootMetaClass__superClass,rootMetaClass__superClass);
复制代码

打印

NSObject - 0x1eb780d08
复制代码

可以看到,根元类的父类是根类

graph BT
根元类 --> 根类

最终我们得出isa和继续的类结构图【 ——> 是继承链,- -> 是isa指向链】

graph LR
根类对象 .-> 根类 .-> 根元类
父类对象 .-> 父类 .-> 父类的元类
父类的元类.-> 根元类
子类对象 .-> 子类 .-> 子类的元类
子类的元类.-> 根元类
根元类.-> 根元类
子类 --> 父类 --> 根类 --> nil
子类的元类 --> 父类的元类 --> 根元类 --> 根类

和苹果官网的图是一致的

isa流程图.png

三、源码分析类的结构

我们知道类的对象有isa,有成员变量,那类的结构呢

image.png

通过源码,我们可以知道类是一个objc_class类型的结构体指针

image.png

查看objc_class的底层源码,可以看到objc_class是继承于objc_object,类里面的主要东西

    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
复制代码

superclass 我们知道是父类,那cachebits是什么东西,可以看到注释写class_rw_t,源码拉到下面

class_rw_t *data() const {
        return bits.data();
    }
复制代码

class_rw_t 返回 bits.data,点进class_rw_t可以看到有方法列表和属性列表

struct class_rw_t {
    ...
    const method_array_t methods() const {
        ...
    }

    const property_array_t properties() const {
        ...
    }
}
复制代码

四、指针和内存平移

现在我们知道一个类,需要获取它的bits,拿到类里面的数据,类结构里面的数据也是连续的很像数组,我们知道数组是存取相同数据类型的一种结构,且在内存段里面数据是连续的,而且每个数据的大小是一样的,我们试着用内存去获取数组的数据

先定义一个数组,用指针指向这个数组

int intArr[4] = {1,2,3,4};
int *p = intArr;
TYLog(@"%p",&intArr);
TYLog(@"%p - %p - %p",&intArr[0],&intArr[1],&intArr[2]);
TYLog(@"%p - %p - %p",p+0,p+1,p+2);
复制代码

进行打印

TY打印: 0x16fdff350
TY打印: 0x16fdff350 - 0x16fdff354 - 0x16fdff358
TY打印: 0x16fdff350 - 0x16fdff354 - 0x16fdff358
复制代码

可以看到数组的地址第1个元素的地址也是数组的首地址,对数组指针平移1个int类型单位会指向下一个数组元素,接下来用循环打印验证一下

for (int i = 0; i < 4; i++) {
    int value = *(p + i);
    TYLog(@"%d",value);  
}
复制代码

打印结果

TY打印: 1
TY打印: 2
TY打印: 3
TY打印: 4
复制代码

五、类的结构内存计算

我们知道类的地址就是类内存结构的首地址,遵循上面的思路,那我们是否能够平移特定大小去获取类结构里面的其他东西

跑下代码

image.png

打印一下类的内存结构

(lldb) x/4gx ApplePerson.class
0x100008468: 0x0000000100008440 0x00000001f7d48d08
0x100008478: 0x000000018c7e0e60 0x0000802900000000
(lldb) 
复制代码

看回类的结构

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
…
}
复制代码

类的第一个地址是ISA,这个我们之前已经验证过了,第二个是superclass,我们测试一下

(lldb) po 0x00000001f7d48d08
NSObject
复制代码

NSObject的确实父类

@interface ApplePerson : NSObject
复制代码

要拿到class_data_bits_t的地址需要类的首地址 + isa长度 + superclass长度 + cache长度,isa8字节,superclass是结构体指针,也是8字节,cache_t是一个结构体,里面的静态变量和方法我们不看,成员变量是下面两个

struct cache_t {
…
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
 …
}
复制代码

uintptr_t8位,mask_t查看一下

#if __LP64__
typedef uint32_t mask_t;
复制代码

uint32_t是位,根据类的内存结构8字节对齐,所以cache_t16字节,这样我们可以得出类的地址平移 8 + 8 + 16 = 32 = 0x20 可以得到bits

六、lldb分析类的结构和类的bits数据分析

image.png

再次打印类的内存结构

(lldb) x/6gx ApplePerson.class
0x1000083d0: 0x00000001000083a8 0x0000000100379140
0x1000083e0: 0x000000010036d0e0 0x0000802800000000
0x1000083f0: 0x0000000100992be4 0x0000000100008420
复制代码

0x1000083d0 偏移32

(lldb) p/x 0x1000083d0 + 0x20
(long) $1 = 0x00000001000083f0
复制代码

和上面的0x1000083f0是一致的,由于要验证的bitsclass_data_bits_t类型,所以强转

(lldb) p (class_data_bits_t *)0x00000001000083f0
(class_data_bits_t *) $2 = 0x00000001000083f0
复制代码

这时候会拿到bits,通过源码的class_rw_t

class_rw_t *data() const {
    return bits.data();
}
复制代码

发现需要的是bits里面的数据bits.data返回*data()

(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100992be0
复制代码

返回的$3就是class_rw_t *类型,和源码class_rw_t *data() 返回值类型一致,打印一下$3

(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000200
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
复制代码

上面还没看到我们想要的数据,我们查询一下 struct class_rw_t

const method_array_t methods() const {
      ...
}

const property_array_t properties() const {
      ...
}
复制代码

有个 methods()properties()两个结构体方法,获取一下

lldb) p $3.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008138
      }
      arrayAndFlag = 4295000376
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
复制代码

里面有个list

(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
  ptr = 0x0000000100008138
}
复制代码

出现一个ptr

(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008138
复制代码

直接获取

(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
复制代码

返回类型是property_list_t,查看一下

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

复制代码

查看entsize_list_tt,发现get(number)会返回数据

struct entsize_list_tt {
…
    Element& get(uint32_t i) const { 
       ASSERT(i < count);
       return getOrEnd(i);
    }
…
}
复制代码

再查看property_t

struct property_t {
    const char *name;
    const char *attributes;
};
复制代码

get(number)方法获取一下

(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
p $8.get(2)
Assertion failed: (i < count), function get, file /Users/…/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
复制代码

可以看到类的属性参数一致,但成员变量subject是没有,打印类的方法

(lldb) p $3.methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100003d70
      }
      arrayAndFlag = 4294983024
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
复制代码

拿到list

(lldb) p $4.list
(const method_list_t_authed_ptr<method_list_t>) $5 = {
  ptr = 0x0000000100003d70
}
复制代码

拿到ptr

(lldb) p $5.ptr
(method_list_t *const) $6 = 0x0000000100003d70
复制代码

获取方法列表

(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 5)
}
复制代码

count = 5,有5个方法,methods方法是method_array_t类型

const method_array_t methods() const {
    …
}
复制代码

查看method_array_t,是个list_array_tt结构

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    ...
}
复制代码

打印

(lldb) p $7.get(0).name()
(SEL) $8 = "sayHenXi"
(lldb) p $7.get(1).name()
(SEL) $9 = "name"
(lldb) p $7.get(2).name()
(SEL) $10 = "setName:"
(lldb)  p $7.get(3).name()
(SEL) $11 = "nickName"
(lldb)  p $7.get(4).name()
(SEL) $12 = "setNickName:"
(lldb) p $7.get(5).name()
Assertion failed: (i < count), function get, file /Users/…/runtime/objc-runtime-new.h, line 624.
复制代码

对比下xcode代码

@interface ApplePerson : NSObject{
    NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;

- (void)sayHenXi;
+ (void)sayNiuBi;
@end
复制代码

可以看到有5个方法,4个是属性的set/get方法,一个是成员方法sayHenXi,而类方法sayNiuBi并没有在类的bits.data的方法列表里面

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享