iOS-7.OC类结构解析

490 阅读12分钟

ios底层文章汇总

1.对象,isa,类和元类

1.1.概念

  • 每个对象都有一个isa,在对象分配内存时,内存的第0-7字节存储的就是isa信息
  • 对象的isa中包含了对象所属类的信息
  • 每个类也有一个isa,也存在类内存的第0-7字节,因为类也是一个对象,类是元类的对象,元类是对类对象的描述,就像类是普通实例对象的描述一样,类的归属来着于元类
  • 元类的定义和创建由编译器完成,元类本与类相关联,名称和类名同名.
  • NSObject的根元类指向NSObject根元类,根元类的isa指向自己

屏幕截图 22-07-2021 15.35.16.png

根元类的地址是0x00000001003340f0,而根元类的isa & mask = isa(0x00000001003340f0 & 0x00007ffffffffff8ULL= 0x00000001003340f0),验证了根元类的isa指向自己

1.2 继承关系

  • 在OC中,NSObject的父类是nil,如果类存在继承关系,则类的元类也存在继承关系

例如:

当有Student类继承自Person类,Person类继承自NSObject时,Student类的元类继承自Person类的元类,Person类的元类继承自NSObject的元类,NSObject的元类就是NSObject本身

  •  之间 的继承关系:

    • 类(subClass) 继承自 父类(superClass)
    • 父类(superClass) 继承自 根类(Root Class),此时的根类是指NSObject
    • 根类 继承自 nil,所以根类NSObject可以理解为万物起源,即无中生有
  • 元类也存在继承,元类之间的继承关系如下:

    • 子类的元类(metal SubClass) 继承自 父类的元类(metal SuperClass)
    • 父类的元类(metal SuperClass) 继承自 根元类(Root metal Class
    • 根元类(Root metal Class) 继承于 根类(Root class),此时的根类是指NSObject
  • 对象,类,元类之间的继承关系:继承关系是相对于类和元类,对象之间不存在继承关系 图转自LG-Cooci老师

由此可知,**isa的走位关系**图如下:

  • 实例对象(Instance of Subclass)的 isa 指向 类(class)
  • 类对象(class) isa 指向 元类(Meta class)
  • 元类(Meta class)isa 指向 根元类(Root metal class)
  • 根元类(Root metal class) 的isa 指向它自己本身,形成闭环,这里的根元类就是NSObject

实例分析:Person继承自NSObject, Student继承自Person

  • isa 走位链(两条)

    • student的isa走位链:student(子类对象) --> Student (子类)--> Student(子元类) --> NSObject(根元类) --> NSObject(跟根元类,即自己)
    • person的isa走位图:person(父类对象) --> Person (父类)--> Person(父元类) --> NSObject(根元类) --> NSObject(跟根元类,即自己)
  • superclass走位链(两条)

    • 类的继承关系链:Student(子类) --> Person(父类) --> NSObject(根类)--> nil
    • 元类的继承关系链:Student(子元类) --> Person(父元类) --> NSObject(根元类)--> NSObject(根类)--> nil

1.3 类编译后在内存中只存在一份

//MARK: - 分析类在内存存在个数
void lgTestClassNum(){
    Class class1 = [HTPerson class];
    Class class2 = [HTPerson alloc].class;
    Class class3 = object_getClass([HTPerson alloc]);
    Class class4 = [HTPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
    /*
     打印结果:
     0x100008100-
     0x100008100-
     0x100008100-
     0x100008100
     */

}

上述代码中,通过三种不同方式创建LGPerson三个类对象,但三个类对象的地址一模一样,说明类在内存中只有一个.

注意:特殊NSObjec类

    // 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);

    /*打印结果:

     0x10070d950 实例对象
     0x100357140 类
     0x1003570f0 元类
     0x1003570f0 根元类
     0x1003570f0 根根元类

     */

在内存中,NSObject类也只存在一份,NSObject的元类和类同名,也存在一份,NSObject的元类、根元类、根根元类相同,NSObject的元类的isa指向自己

2 objc_object 和 objc_class

  • 所有的类都是objc_class的对象,objc_class继承自结构体objc_object

//注意:源码来自objc4-781 objc_runtime_new.h
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;            
    class_data_bits_t bits;    

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    //...
}

注意:源码来自objc4-781 objc_runtime_new.h

objc_class 与 objc_object 有什么关系?

  • 结构体类型objc_class 继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性

  • mian.cpp底层编译文件中,NSObject中的isa在底层是由Class 定义的,其中class的底层编码来自 objc_class类型,所以NSObject也拥有了isa属性

  • NSObject 是一个类,用它初始化一个实例对象objc,objc 满足 objc_object 的特性(即有isa属性),主要是因为isa 是由 NSObject 从objc_class继承过来的,而objc_class继承自objc_objectobjc_object 有isa属性。所以对象都有一个 isa,isa表示指向,来自于当前的objc_object

  • objc_object(结构体) 是 当前的  根对象所有的对象都有这样一个特性 objc_object,即拥有isa属性

  • objc_object是根对象,是一个结构体,objc_object含有一个isa的成员.所以元类,类,对象都有isa
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
    

objc_object 与对象的关系:

  • 所有的对象都是以objc_object为模板实例化出来的;OC端类的根类是NSObject,NSObject在底层的模板是objc_object

  • OC代码都会编译成C++代码,OC中的Class(isa指针的类型)的底层模板是objc_class: typedef struct objc_class *Class;

3 类结构分析

3.1 内存偏移

存在在栈内存中的数据地址连续:

  • 普通基本类型的变量的地址偏移
 int a = 10;
 int b = 10;
 printf("%d--%p\n",a,&a);
 printf("%d--%p\n",b,&b);
  /*
   打印结果:
   10--0x7ffeefbff4f0
   10--0x7ffeefbff4f4
 */

在内存中,常量区有个地址空间存在着常量10,定义a、b两个变量在编译时,编译int a = 10会将常量10进行值拷贝到变量a中,编译int b = 10将常量10进行值拷贝到变量b中,但是存储变量a、b的地址不一样.

a,b存储在栈中,地址连续,地址相处a的类型所在需要的内存空间,例子中a是int型,所有a和b的地址相差4个字节

  • 类对象的变量地址偏移
 LGPerson *p1 = [LGPerson alloc]; // p1 是指针
 LGPerson *p2 = [LGPerson alloc];
 NSLog(@"%@ -- %p\n", p1, &p1);
 NSLog(@"%@ -- %p\n", p2, &p2);
/*
  打印结果:
  <LGPerson: 0x10180f430> -- 0x7ffeefbff4e0
  <LGPerson: 0x10180f730> -- 0x7ffeefbff4e8
 */

p1、p2 是指针,p1 ,p1是 指向 [LGPerson alloc]创建的空间地址 &p1、&p2是 指向 p1、p2对象指针的地址,二级指针

  • 数组指针地址偏移
 int c[4] = {1, 2, 3, 4};
 int *d = c;
 NSLog(@"%p -- %p - %p",&c[0], &c[1],&c[2]);
 NSLog(@"%p -- %p - %p", d, d+1, d+2);
 /*
  0x7ffeefbff4e0 -- 0x7ffeefbff4e4 - 0x7ffeefbff4e8
  0x7ffeefbff4e0 -- 0x7ffeefbff4e4 - 0x7ffeefbff4e8
         
*/

数组元素的地址是连续的,且&c[0] 与 &c[1] 相差4个字节,&c[1] 与 &c[2] 相差4个字节,地址之间相差的字节数,由存储的数据类型决定,由于存储的是int型,所以是相差4个字节

可以通过 首地址+偏移量取出数组中的其他元素,其中偏移量是数组的下标,实际取的地址是数组首地址+ 偏移量 * 数据类型字节数

3.2 objc_class类结构

objc4-781 Project Headers/objc_runtime-new.h定义objc_class结构如下:

  • isa属性:Class类型的的isa,占8字节

  • superclass 属性:Class类型,Class是objc_object指针,占8字节

  • cache属性:cache_t结构体类型对象,占用16个字节

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
    explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
    mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
    
#if __LP64__
    uint16_t _flags;  //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
    uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

#if 分支计算内存

buckets 类型是struct bucket_t *,是结构体指针类型,占8字节

mask 是mask_t 类型,而 mask_t , unsigned int 的别名,占4字节

_flags 是uint16_t类型,占 2个字节

_occupied 是uint16_t类型,占 2个字节

elseif分支计算内存

_maskAndBuckets 是uintptr_t类型,是指针,占8字节

_mask_unused 是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节

_flags 是uint16_t类型,占 2个字节

_occupied 是uint16_t类型,占 2个字节

  • bits属性:class_data_bits_t类型,是个结构体,结构体的内存大小需要根据内部的属性来确定,首地址经过上面前3个属性的内存大小总和32个字节平移,就可以获取到bits的首地址

3.3 获取bits的地址

3.3.1 通过类的首地址平移32字节获取 class_data_bits_t*:

(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002240 LGPerson
(lldb) x/4gx 0x0000000100002240
0x100002240: 0x0000000100002218 0x0000000100334140
0x100002250: 0x00000001006847d0 0x000580240000000f
(lldb) p 0x0000000100002218 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 4294976024
(lldb) p/x 0x0000000100002218 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100002218
(lldb) p (class_data_bits_t*)0x0000000100002238
(class_data_bits_t *) $3 = 0x0000000100002238

3.3.2 class_data_bits_t通过data()方法获取class_rw_t结构体指针

  • class_data_bits_t结构
  • 命令获取:
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002240 LGPerson
(lldb) x/4gx 0x0000000100002240
0x100002240: 0x0000000100002218 0x0000000100334140
0x100002250: 0x00000001006847d0 0x000580240000000f
(lldb) p 0x0000000100002218 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 4294976024
(lldb) p/x 0x0000000100002218 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100002218
(lldb) p (class_data_bits_t*)0x0000000100002238
(class_data_bits_t *) $3 = 0x0000000100002238
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000100683dc0
(lldb) p $4->methods()

3.3.4 class_rw_t通过methods(),properties(),protocols()获取方法列表,属性列表,协议列表

  • class_rw_t结构定义
  • 命令获取类方法列表(类方法存储在元类中,从元类中获取)
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002240 LGPerson
(lldb) x/4gx 0x0000000100002240
0x100002240: 0x0000000100002218 0x0000000100334140
0x100002250: 0x00000001006847d0 0x000580240000000f
(lldb) p 0x0000000100002218 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 4294976024
(lldb) p/x 0x0000000100002218 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100002218
(lldb) p (class_data_bits_t*)0x0000000100002238
(class_data_bits_t *) $3 = 0x0000000100002238
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000100683dc0
(lldb) p $4->methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002090
      arrayAndFlag = 4294975632
    }
  }
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002090
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayBye"
      types = 0x0000000100000f6e "v16@0:8"
      imp = 0x0000000100000d20 (KCObjc`+[LGPerson sayBye])
    }
  }
}
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "sayBye"
  types = 0x0000000100000f6e "v16@0:8"
  imp =
  • 命令获取对象方法(从类结构中获取)
lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002240 LGPerson
(lldb) p/x (class_data_bits_t *)(0x0000000100002260)
(class_data_bits_t *) $1 = 0x0000000100002260
(lldb) p/x $1->data()
(class_rw_t *) $2 = 0x0000000100656070
(lldb) p/x $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000020f8
      arrayAndFlag = 0x00000001000020f8
    }
  }
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x00000001000020f8
(lldb)  p *$4
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 16
    count = 2
    first = {
      name = "name"
      types = 0x0000000100000ee1 "T@\"NSString\",C,N,V_name"
      imp = 0x0000000100000ef9 ("age")
    }
  }
}
(lldb)p $8.get(0)
(method_t) $9 = {
  name = "name"
  types = 0x0000000100000ee1 "T@\"NSString\",C,N,V_name"
  imp = 0x0000000100000ef9 ("age")
}
(lldb) p $8.get(1)
(method_t) $10 = {
  name = "age"
  types = 0x0000000100000efd "Tq,N,V_age"
  imp = 0x0000000100002240 ((void *)0x0000000100002218: LGPerson)
}
(lldb) p $8.get(2)
Assertion failed: (i < count), function get, file /Users/wangqiao/Desktop/iOSV9.0/可编译objc源码/runtime/objc-runtime-new.h, line 434.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 命令获取对象属性(从类结构中获取)
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002240 LGPerson
(lldb) p/x (class_data_bits_t *)(0x0000000100002260)
(class_data_bits_t *) $1 = 0x0000000100002260
(lldb) p/x $1->data()
(class_rw_t *) $2 = 0x0000000100656070
(lldb) p/x $2->properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000021d8
      arrayAndFlag = 0x00000001000021d8
    }
  }
}
(lldb) p/x $5.list
(property_list_t *const) $6 = 0x00000001000021d8
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 2
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}
(lldb) p $7.get(0)
(property_t) $11 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $7.get(1)
(property_t) $12 = (name = "age", attributes = "Tq,N,V_age")
(lldb) p $7.get(2)
Assertion failed: (i < count), function get, file /Users/wangqiao/Desktop/iOSV9.0/可编译objc源码/runtime/objc-runtime-new.h, line 434.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 
  • 命令获取属性列表(不包含成员变量)
    • 通过objc_class类的地址便宜32个字节,得到bits的地址,获取到class_data_bits_t的地址class_data_bits_t*

    • class_data_bits_t中调用data(),获取到class_rw_t的地址class_rw_t*

    • class_rw_t结构中调用ro(),获取到class_ro_t的地址`class_ro_t*)

    • class_ro_t结构中的baseProperties成员变量,就是属性列表的地址

    • llvm命令获取属性列表(同理可以获取成员变量列表方法列表)

  • 通过bits->data->ro 获取ivars(包含属性和成员变量)
(lldb) x/4gx person
0x100647220: 0x011d8001000083dd 0x0000000000000000
0x100647230: 0x0000000000000000 0x0000000000000000
(lldb) p 0x011d8001000083dd & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 4295001048
(lldb) p/x  0x011d8001000083dd & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000083d8
(lldb) p (class_data_bits_t *)0x00000001000083f8
(class_data_bits_t *) $3 = 0x00000001000083f8
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000100646670
(lldb) p $4->ro()
(const class_ro_t *) $5 = 0x0000000100008098
(lldb) p *$5
(const class_ro_t) $6 = {
  flags = 388
  instanceStart = 8
  instanceSize = 72
  reserved = 0
   = {
    ivarLayout = 0x0000000100003ede "\x01\x11\x13"
    nonMetaclass = 0x0000000100003ede
  }
  name = {
    std::__1::atomic<const char *> = "HTPerson" {
      Value = 0x0000000100003ed5 "HTPerson"
    }
  }
  baseMethodList = 0x00000001000080e0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008190
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008298
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x0000000100008190
(lldb) p $7->get(0)
(ivar_t) $8 = {
  offset = 0x0000000100008370
  name = 0x0000000100003eec "hobby"
  type = 0x0000000100003f60 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
 
(lldb) p $7->get(1)
(ivar_t) $9 = {
  offset = 0x0000000100008378
  name = 0x0000000100003ef2 "weight"
  type = 0x0000000100003f6c "i"
  alignment_raw = 2
  size = 4
}
 
(lldb) p $7->get(2)
(ivar_t) $10 = {
  offset = 0x0000000100008380
  name = 0x0000000100003ef9 "objc"
  type = 0x0000000100003f6e "@\"NSObject\""
  alignment_raw = 3
  size = 8
}
 
(lldb) p $7->get(3)
(ivar_t) $11 = {
  offset = 0x0000000100008388
  name = 0x0000000100003efe "sex"
  type = 0x0000000100003f7a "c"
  alignment_raw = 0
  size = 1
}
 
(lldb) p $7->get(4)
(ivar_t) $12 = {
  offset = 0x0000000100008390
  name = 0x0000000100003f02 "intersting"
  type = 0x0000000100003f60 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

(lldb) p $7->get(5)
(ivar_t) $13 = {
  offset = 0x0000000100008398
  name = 0x0000000100003f0d "_name"
  type = 0x0000000100003f60 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7->get(6)
(ivar_t) $14 = {
  offset = 0x00000001000083a0
  name = 0x0000000100003f13 "_nickname"
  type = 0x0000000100003f60 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7->get(7)
(ivar_t) $15 = {
  offset = 0x00000001000083a8
  name = 0x0000000100003f1d "_age"
  type = 0x0000000100003f7c "q"
  alignment_raw = 3
  size = 8
}

3.4 OC层通过runtime获取实例变量名称,属性的名称

void lgObjc_copyIvar_copyProperies(Class pClass){
   
   unsigned int count = 0;
   Ivar *ivars = class_copyIvarList(pClass, &count);
   for (unsigned int i=0; i < count; i++) {
       Ivar const ivar = ivars[i];
       //获取实例变量名
       const char*cName = ivar_getName(ivar);
       NSString *ivarName = [NSString stringWithUTF8String:cName];
       LGLog(@"class_copyIvarList:%@",ivarName);
   }
   free(ivars);

   unsigned int pCount = 0;
   objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
   for (unsigned int i=0; i < pCount; i++) {
       objc_property_t const property = properties[i];
       //获取属性名
       NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
       
       LGLog(@"class_copyProperiesList:%@",propertyName);
   }
   free(properties);
}