iOS底层原理-类的原理探究【上】

216 阅读5分钟

一、ISA分析到元类

 LGPerson *p = [[LGPerson alloc] init];
 Class class1= [LGPerson class];
 Class class2= p.class;
 Class class3= object_getClass(p);
 Class class4= object_getClass([LGPerson alloc]);

NSLog(@"\n%p-\n%p-\n%p-\n%p-",class1,class2,class3,class4);

Class class5= object_getClass(class3);
NSLog(@"\n%p",class5);
输出结果:
0x100dc54d0-
0x100dc54d0-
0x100dc54d0-
0x100dc54d0-

0x100dc54a8
(lldb) po 0x100dc54d0
LGPerson
(lldb) po 0x100dc54a8
LGPerson

总结

1、类对象的地址都是一样的,内存中每一个类只有一块内存

2、LGPerson类有两个不一样的地址,但是一个类对象只有一个地址。0x100dc54d0LGPerson的类地址,那么0x100dc54a8这个类地址苹果把它叫做元类

结合lldb以及isamask进行探究:

isa发现元类.png

  • 实例对象isa -->

  • isa--> 元类

  • 元类isa--> 根元类(NSObject的isa)

  • 根元类isa--> 根元类(指向自己)

二、ISA走位图和继承链

  NSLog(@"*********************************************");
    NSLog(@"\n实例对象地址 - %p",objc);
    NSLog(@"\n类对象地址 - %p",[objc class]);
    NSLog(@"\n父类对象地址 - %p",[objc superclass]);
    NSLog(@"\n元类对象地址 - %p",object_getClass([objc class]));
    NSLog(@"\n元类的父类对象地址 - %p",[object_getClass([objc class]) superclass]);
    NSLog(@"\n根元类对象地址 - %p",object_getClass(object_getClass([objc class])));
    NSLog(@"\n根元类的父类对象地址 - %p",[object_getClass(object_getClass([objc class])) superclass]);
    NSLog(@"\n根根元类对象地址 - %p",object_getClass(object_getClass(object_getClass([objc class]))));
    NSLog(@"\n根根元类的父类对象地址 - %p",[object_getClass(object_getClass(object_getClass([objc class]))) superclass]);
    NSLog(@"*********************************************");

结果以及lldb调试:

*********************************************
实例对象地址 - 0x281476400 // Student的实例对象地址
类对象地址 - 0x1025d55b8 // Student的类对象地址
父类对象地址 - 0x1025d5568 // Student的父类对象是Person的类对象
元类对象地址 - 0x1025d5590 // Student的元类对象地址
元类的父类对象地址 - 0x1025d5540 // Person的元类对象地址
根元类对象地址 - 0x1db235de0 // NSObject元类对象地址
根元类的父类对象地址 - 0x1db235e08 // NSObject 类对象地址
根根元类对象地址 - 0x1db235de0 // NSObject元类对象地址 
根根元类的父类对象地址 - 0x1db235e08// NSObject 类对象地址
*********************************************

(lldb) p/x [LGStudent class] // Student的类对象地址
(Class) $0 = 0x00000001025d55b8 LGStudent
(lldb) p/x [LGPerson class] // Person的类对象地址
(Class) $1 = 0x00000001025d5568 LGPerson
(lldb) p/x [NSObject class] //NSObject的类对象地址
(Class) $2 = 0x00000001db235e08 NSObject
(lldb) p/x $0.superclass // student的父类是person
(Class) $3 = 0x00000001025d5568 LGPerson
(lldb) p/x $1.superclass // person的父类是NSObject
(Class) $4 = 0x00000001db235e08 NSObject
(lldb) p/x $2.superclass // NSOject的父类是nil
(Class) $5 = nil
(lldb) p/x object_getClass($0)// Student元类
(Class _Nullable) $6 = 0x00000001025d5590
(lldb) p/x object_getClass($1)// Person元类
(Class _Nullable) $7 = 0x00000001025d5540
(lldb) p/x object_getClass($2)// NSObject元类
(Class _Nullable) $8 = 0x00000001db235de0
(lldb) p/x $6.superclass// Student元类的父类是 Person元类
(Class) $9 = 0x00000001025d5540
(lldb) p/x $7.superclass// Person元类 的父类 NSObject元类
(Class) $10 = 0x00000001db235de0
(lldb) p/x $8.superclass// Person元类 的父类 NSObject类对象
(Class) $11 = 0x00000001db235e08 NSObject
(lldb) p/x object_getClass($8)// Person元类 的元类 是其本身
(Class _Nullable) $12 = 0x00000001db235de0
  

结论:

isa流程图.png

三、分析类的结构

3.1 源码分析类的结构

object2.0(基于objc4(818版本)的源码)

源码位置为objc-runtime-new.h文件

struct objc_class : objc_object {
    ...
    // Class ISA; //8字节
    Class superclass; // 8字节
    cache_t cache;    //16字节   // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
		... 省略大部分源码
}

源码分析objc_class继承objc_objectobjc_object里面只有一个成员变量isaisa具体是作用已经探究过。

可以通过首地址+偏移值来获取里面的成员变量的地址,然后获取值去探究这三个成员变量的具体作用,但是偏移值需要知道当前变量之前的所有成员变量的大小

  • isa 结构体指针占8字节

  • Class superclass 也是结构体指针占8字节

  • cache_t cache是结构体类型的大小,由结构体内成员变量决定

  • class_data_bits_t bits 知道前面三个成员变量大小,就可以得到bits的地址

只要知道cache_t的内存大小,objc_class的所有的成员变量都可以获取到相应的地址,探究下cache_t内存大小:16字节

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union { // 联合体大小 8
        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
    };
    ...
}

cache_t 是结构体类型,有两个成员变量_bucketsAndMaybeMask和一个联合体

  • _bucketsAndMaybeMaskuintptr_t无符长整型占8字节
  • 联合体里面有两个成员变量结构体_originalPreoptCache,联合体的内存大小由成员变量中的最大变量类型决定
  • _originalPreoptCache 是结构体指针占8字节
  • 结构体中有_maybeMask_flags_occupied_maybeMaskuint32_t4字节_flags_occupieduint16_t 各占2字节,结构体大小是8字节
  • cache_t的内存大小是 8+8 或者是8+4+2+2都是16字节

探究类里面的各个成员变量,成员变量的内存地址如下

  • isa的内存地址是首地址
  • superclass的内存地址是首地址+0x8
  • cache的内存地址是首地址+0x10
  • bits的内存地址是首地址+0x20

3.2 分析properties()

分析属性列表, class_data_bits_t -> class_rw_t -> properties()

(lldb) x/4gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100619e90 0x0002802800000003
(lldb) p/x 0x100008380+0x20 // 结构体指针偏移到 bits 的位置, 8(ISA) + 8(superclass) + 16(cache)
(long) $1 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100619ce0
(lldb) p $3.properties
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008260
      }
      arrayAndFlag = 4295000672
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) p $4.list.ptr
(property_list_t *const) $5 = 0x0000000100008260
(lldb) p *$5
(property_list_t) $6 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb) p $6.get(2)
Assertion failed: (i < count), function get... // 越界

3.3 分析methods()

分析方法列表, class_data_bits_t -> class_rw_t -> methods():

(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008380 LGPerson
(lldb) p/x 0x0000000100008380+0x20
(long) $1 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010079a710
(lldb) p $3.methods
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008160
      }
      arrayAndFlag = 4295000416
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
(lldb) p $4.list.ptr
(method_list_t *const) $5 = 0x0000000100008160
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $6.get(0).big()
(method_t::big) $7 = {
  name = "sayNB"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}
(lldb) p $6.get(1).big()
(method_t::big) $8 = {
  name = "hobby"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003db0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $6.get(2).big()
(method_t::big) $9 = {
  name = "setHobby:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003de0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $6.get(3).big()
(method_t::big) $10 = {
  name = "init"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $6.get(4).big()
(method_t::big) $11 = {
  name = "name"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson name])
}
(lldb) p $6.get(5).big()
(method_t::big) $12 = {
  name = "setName:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setName:])
}
(lldb) p $6.get(6).big()
Assertion failed: (i < count), function get... //越界
  • 1、属性的get和设置方法也在里面
  • 2、只声明未实现的方法不会展示
  • 3、发现类方法不在该处,那是不是在元类里面呢,下面观察一下
(lldb) p object_getClass(LGPerson.class)// 得到元类的内存地址

(Class) $0 = 0x00000001000083a8
(lldb) p/x 0x00000001000083a8+0x20
(long) $1 = 0x00000001000083c8
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000083c8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100679810
(lldb) p $3->methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000082d0
      }
      arrayAndFlag = 4295000784
    }
  }
}
(lldb) p $4.list.ptr
(method_list_t *const) $5 = 0x00000001000082d0
(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 = "sayHello_MyClassMethod"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003e10 (KCObjcBuild`+[LGPerson sayHello_MyClassMethod])
}

此时, 我们在元类中找到了类方法 sayHello_MyClassMethod

3.4 分析 ro()->ivars

分析成员列表, class_data_bits_t -> class_rw_t -> class_ro_t -> ivars:

(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008380 LGPerson
(lldb) p/x 0x0000000100008380+0x20
(long) $1 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010071dc70
(lldb) p $3->ro()
(const class_ro_t *) $4 = 0x0000000100008118
(lldb) p $4->ivars
(const ivar_list_t *const) $5 = 0x00000001000081f8
(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 = 0x0000000100008318
  name = 0x0000000100003f1f "_myVar"
  type = 0x0000000100003f7e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $6.get(1)
(ivar_t) $8 = {
  offset = 0x0000000100008320
  name = 0x0000000100003f26 "_name"
  type = 0x0000000100003f7e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $6.get(2)
(ivar_t) $9 = {
  offset = 0x0000000100008328
  name = 0x0000000100003f2c "_hobby"
  type = 0x0000000100003f7e "@\"NSString\""
  alignment_raw = 3
  size = 8
}

类的结构图

最后我们画一下类的结构图

UML类图 (2).png