OC对象,isa和superclass

instance对象实例对象实例对象

class对象类对象类对象

meta-class对象元类对象元类对象

一.instance对象

概念: instance对象就是通过类alloc出来的对象,每次调用alloc 都会产生新的instance对象

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
复制代码

object1、object2是NSObject的instance对象实例对象实例对象

它们是不同的两个对象,分别占用两块不同的内存

instance对象在内存中存储的信息包括:

  • isa指针

  • 其他成员变量

对象的isa指针指向哪里?

截屏2020-06-02 上午11.04.17

截屏2020-06-02 上午11.04.17

instance的isa指针指向class。
当调用对象方法时,通过instance的isa指针找到class,最后找到对象方法的实现进行调用。

class的isa指针指向meta-class。
当调用类方法时,通过class的isa指针找到meta-class,最后找到类方法的实现进行调用。

二.class对象

Class objcClass1 = [object1 class];
Class objcClass2 = [object2 class];
Class objcClass3 = object_getClass(object1);
Class objcClass4 = object_getClass(object2);
Class objcClass5 = [NSObject class];
复制代码

objcClass1~objcClass5都是NSObject的class对象类对象类对象

它们都是同一个对象,每个类在内存中有且只有一个class对象 -> NSObject

class对象在内存中存储的信息包括:

  • isa指针(class的isa指针指向meta-class。
    当调用类方法时,通过class的isa指针找到meta-class,最后找到类方法的实现进行调用。)
  • superclass指针
  • 类的属性@𝑝𝑟𝑜𝑝𝑒𝑟𝑡𝑦@property、类的对象方法信息𝑖𝑛𝑠𝑡𝑎𝑛𝑐𝑒𝑚𝑒𝑡ℎ𝑜𝑑instancemethod
  • 类的协议信息𝑝𝑟𝑜𝑡𝑜𝑐𝑜𝑙protocol、类的成员变量信息𝑖𝑣𝑎𝑟ivar

class对象的superclass指针

截屏2020-06-02 上午11.06.38

截屏2020-06-02 上午11.06.38

当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用。

三.meta-class对象

// 将类对象作为参数传入,获得元类对象
Class objcMetaClass = object_getClass([NSObject class]);
复制代码

objcMetaClass是NSObject的meta-class元类对象元类对象

每个类在内存中有且只有一个meta-class对象

meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:

  • isa指针
  • superclass指针
  • 类的类方法信息𝑐𝑙𝑎𝑠𝑠𝑚𝑒𝑡ℎ𝑜𝑑classmethod

meta-class对象的superclass指针

截屏2020-06-02 上午11.09.34

截屏2020-06-02 上午11.09.34

注意: 一下代码获取的objectClass是class对象,并不是meta-class对象

Class objectClass = [[NSObject class] class];
复制代码

补充: 查看Class是否为meta-class

Class objcClass5 = [NSObject class];
BOOL result1 = class_isMetaClass(objcClass5); // NO
BOOL result2 = class_isMetaClass(object_getClass(objcClass5)); // YES

四.object_getClass内部实现

Class objc_getClass(const char *aClassName)
复制代码

传入字符串类名

返回对应的类对象

Class object_getClass(id obj)
复制代码

传入obj可能是instance对象、class对象、meta-class对象

返回值:

  • 如果是instance对象,返回class对象
  • 如果是class对象,返回meta-class对象
  • 如果是meta-class对象,返回NSObject基类基类的meta-class对象
- (Class)class,+(Class)class
复制代码

返回值就是类对象

isa细节

从64bit开始,isa需要进行一次位运算,才能计算出真实地址

截屏2020-06-02 上午11.15.38

截屏2020-06-02 上午11.15.38

截屏2020-06-02 上午11.16.20

截屏2020-06-02 上午11.16.20

isa、superclass总结

截屏2020-06-02 上午11.29.44

截屏2020-06-02 上午11.29.44

实例对象𝑖𝑛𝑠𝑡𝑎𝑛𝑐𝑒instance的isa指向class

类对象𝑐𝑙𝑎𝑠𝑠class的isa指向meta-class

元类对象𝑚𝑒𝑡𝑎−𝑐𝑙𝑎𝑠𝑠meta−class的isa指向基类的meta-class

类对象𝑐𝑙𝑎𝑠𝑠class的superclass指向父类的class

  • 如果没有父类,superclass指针为nil

元类对象𝑚𝑒𝑡𝑎−𝑐𝑙𝑎𝑠𝑠meta−class的superclass指向父类的meta-class

  • 基类的meta-class的superclass指向基类的class

instance调用对象方法的轨迹

  • isa找到class,方法不存在,就通过superclass找父类

class调用类方法的轨迹

  • isa找到meta-class,方法不存在,就通过superclass找父类

class和meta-class的结构

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists              BJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
复制代码

在runtime.h头文件中我们可以看到一个条件编译如果不是objc2的版本下,那么条件范围内的代码将不会被执行,所以在OC2.0中,这段代码已经过时了。而且通过OBJC2_UNAVAILABLE也可以看出,struct objc_class这个结构体在OC2.0中已经不可用了。

4.1 窥探struct objc_class的结构

类和元类在内存中的结构,就是struct objc_class这样一个结构体,这个结构体在objc-runtime-new.h头文件中可以找到最新版本的定义。如下图结构体struct objc_class中部分代码所示:

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-用于获取具体的类信息
    ...
    ...
    ...
}
复制代码

注意: 冒号语法是继承的意思,结构体objc_class继承自objc_object,这种语法是C++的语法。

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

图为老版本代码结构,可以参考新版本源码

截屏2020-06-02 下午4.15.02

截屏2020-06-02 下午4.15.02

struct objc_class结构体中包含上面这样一段代码,返回值是class_rw_t,是一个可读写的表单。

进入class_rw_t头文件中我们可以看到一段public的代码,返回值是class_rw_ext_t类型。如下图所示

class_rw_ext_t ext const { *return get_ro_or_rwe.dyn_cast>; }

进入class_rw_ext_t头文件,我们可以看到方法列表、属性列表、协议列表的相关定义。如下图所示

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

进入到class_rw_ext_t结构体中class_ro_t类型的头文件中,我们可以看到instanceSize、ivars的定义。下图为头文件中的部分代码。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    ...
    ...
    ...

截屏2020-06-02 下午4.17.27

截屏2020-06-02 下午4.17.27

总结: OC的类信息存放在哪里?

  • 对象方法、属性、成员变量、协议信息,存放在class对象中。
  • 类方法,存放在meta-class对象中。
  • 成员变量的具体值,存放在instance对象。

拓展

class_ro_t 和 class_rw_t 的区别

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

Cpp

可以看出,class_rw_t结构体内有一个指向class_ro_t结构体的指针。

每个类都对应有一个class_ro_t结构体和一个class_rw_t结构体。在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。

用两张图来说明这个过程:

类的realizeClass运行之前:

1286243-2c06dbdc008c5687

1286243-2c06dbdc008c5687

类的realizeClass运行之后:

1286243-1b121fd2ea224477

1286243-1b121fd2ea224477

细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容

参考:
Objective-C runtime - 属性与方法

Ivar 详解

1.Ivar

1.1Ivar 的类型
typedef objc_ivar * Ivar;
  struct objc_ivar {
     char *ivar_name;
     char *ivar_type;
     int ivar_offset;
  #ifdef __LP64__
     int space;
  #endif
  } 

Cpp

Ivarobjc_ivar的指针,包含变量名,变量类型等成员。

1.2为类添加 Ivar

运行时规定,只能在objc_allocateClassPairobjc_registerClassPair两个函数之间为类添加变量

如下所示:

  //额外空间     未知,通常设置为 0
  Class clazz = objc_allocateClassPair(父类class,类名,额外空间);
  //以NSString*为例
  //变量size sizeof(NSString)
  //对齐     指针类型的为log2(sizeof(NSString*))
  //类型     @encode(NSString*)
  BOOL flag = class_addIvar(clazz,变量名,变量size,对齐,类型);
  objc_registerClassPair(clazz);

Kotlin

1.3 Ivar的相关操作
  //获取Ivar的名称
  const char *ivar_getName(Ivar v);
  //获取Ivar的类型编码,
  const char *ivar_getTypeEncoding(Ivar v)
  //通过变量名称获取类中的实例成员变量
  Ivar class_getInstanceVariable(Class cls, const char *name)
  //通过变量名称获取类中的类成员变量
  Ivar class_getClassVariable(Class cls, const char *name)
  //获取指定类的Ivar列表及Ivar个数
  Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
  //获取实例对象中Ivar的值
  id object_getIvar(id obj, Ivar ivar) 
  //设置实例对象中Ivar的值
  void object_setIvar(id obj, Ivar ivar, id value)

Objectivec

1.4 Ivar的使用
  //在运行时创建继承自NSObject的People类
  Class People = objc_allocateClassPair([NSObject class], "People", 0);
  //添加_name成员变量
  BOOL flag1 = class_addIvar(People, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
  if (flag1) {
      NSLog(@"NSString*类型  _name变量添加成功");
  }
  //添加_age成员变量
  BOOL flag2 = class_addIvar(People, "_age", sizeof(int), sizeof(int), @encode(int));
  if (flag2) {
      NSLog(@"int类型 _age变量添加成功");
  }
  //完成People类的创建
  objc_registerClassPair(People);
  unsigned int varCount;
  //拷贝People类中的成员变量列表
  Ivar * varList = class_copyIvarList(People, &varCount);
  for (int i = 0; i<varCount; i++) {
      NSLog(@"%s",ivar_getName(varList[i]));
  }
  //释放varList
  free(varList);
  //创建People对象p1
  id p1 = [[People alloc]init];
  //从类中获取成员变量Ivar
  Ivar nameIvar = class_getInstanceVariable(People, "_name");
  Ivar ageIvar = class_getInstanceVariable(People, "_age");
  //为p1的成员变量赋值
  object_setIvar(p1, nameIvar, @"张三");
  object_setIvar(p1, ageIvar, @33);
  //获取p1成员变量的值
  NSLog(@"%@",object_getIvar(p1, nameIvar));
  NSLog(@"%@",object_getIvar(p1, ageIvar));

Objectivec

2. Property

2.1 objc_property_t 与 objc_property_attribute_t类型
typedef struct objc_property *objc_property_t;
  //特性
  typedef struct {
      const char *name;           //特性名称
      const char *value;          //特性的值
  } objc_property_attribute_t;

Cpp

特性相关编码
属性的特性字符串 以 T@encode𝑡𝑦𝑝𝑒type 开头, 以 V实例变量名称 结尾,中间以特性编码填充,通过property_getAttributes即可查看

特性编码 具体含义
R readonly
C copy
& retain
N nonatomic
G𝑛𝑎𝑚𝑒name getter=𝑛𝑎𝑚𝑒name
S𝑛𝑎𝑚𝑒name setter=𝑛𝑎𝑚𝑒name
D @dynamic
W weak
P 用于垃圾回收机制

2.2 为类添加Property
  BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

Cpp

2.3 Property的相关操作
  //替换类中的属性
  void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
  //获取类中的属性
  objc_property_t class_getProperty(Class cls, const char *name)
  //拷贝类中的属性列表
  objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
  //获取属性名称
  const char *property_getName(objc_property_t property)
  //获取属性的特性
  const char *property_getAttributes(objc_property_t property) 
  //拷贝属性的特性列表
  objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
  //拷贝属性的特性的值
  char *property_copyAttributeValue(objc_property_t property, const char *attributeName)

Cpp

2.4 Property的使用
  Class People = objc_allocateClassPair([NSObject class], "People", 0);
  objc_registerClassPair(People);
  //T@
  objc_property_attribute_t attribute1;
  attribute1.name = "T";
  attribute1.value=@encode(NSString*);
  //Noatomic
  objc_property_attribute_t attribute2 = {"N",""};//value无意义时通常设置为空
  //Copy
  objc_property_attribute_t attribute3 = {"C",""};
  //V_属性名
  objc_property_attribute_t attribute4 = {"V","_name"};
  //特性数组
  objc_property_attribute_t attributes[] ={attribute1,attribute2,attribute3,attribute4};
  //向People类中添加名为name的属性,属性的4个特性包含在attributes中
  class_addProperty(People, "name", attributes, 4);
  //获取类中的属性列表
  unsigned int propertyCount;
  objc_property_t * properties = class_copyPropertyList(People, &propertyCount);
  for (int i = 0; i<propertyCount; i++) {
      NSLog(@"属性的名称为 : %s",property_getName(properties[i]));
      NSLog(@"属性的特性字符串为: %s",property_getAttributes(properties[i]));
  }
  //释放属性列表数组
  free(properties);

   转载规则


《OC对象,isa和superclass》 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
OC 字符串包含中文sha1加密算法 OC 字符串包含中文sha1加密算法
-(NSString *)sha1 { // see http://www.makebetterthings.com/iphone/how-to-get-md5-and-sha1-in-objective-c-ios-sdk/
2020-09-28
下一篇 
wkwebview的坑:iOS10位分水岭,包括跨域和低版本不能再沙盒访问js,css等资源文件 wkwebview的坑:iOS10位分水岭,包括跨域和低版本不能再沙盒访问js,css等资源文件
1:iOS10以下版本,wkwebview不允许在沙盒里访问html里的css,js等外部资源文件,需要转移到tmp文件夹下访问; 复制到tmp文件夹下,然后从tmp文件夹下访问即可 -(NSString )spineFileMoveToT
2020-09-28
  目录