struct objc_classobjc_class与objc_object

在学习isa之前我么先研究一下objc_class与objc_object之间的关系。

objc_object

我们先看objc_object结构体

  1. struct objc_object {
  2. Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  3. };
  1. typedef struct objc_class *Class;

我们看到objc_object结构体中只有一个Class类型的isa,我们在看Class的定义是一个objc_class的结构体指针。

objc_class

我们在来看objc_class的结构体

  1. struct objc_class {
  2. Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  3. #if !__OBJC2__
  4. Class _Nullable super_class OBJC2_UNAVAILABLE;
  5. const char * _Nonnull name OBJC2_UNAVAILABLE;
  6. long version OBJC2_UNAVAILABLE;
  7. long info OBJC2_UNAVAILABLE;
  8. long instance_size OBJC2_UNAVAILABLE;
  9. struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
  10. struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
  11. struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
  12. struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
  13. #endif
  14. } OBJC2_UNAVAILABLE;

这里我们需要注意这个结构体已经被标注OBJC2_UNAVAILABLE目前是一个已经废弃了的结构体。
其实在当前的objc项目中在用的objc_class的结构体是我们下边的这个结构体

  1. struct objc_class : objc_object {
  2. objc_class(const objc_class&) = delete;
  3. objc_class(objc_class&&) = delete;
  4. void operator=(const objc_class&) = delete;
  5. void operator=(objc_class&&) = delete;
  6. // Class ISA;
  7. Class superclass;
  8. cache_t cache; // formerly cache pointer and vtable
  9. class_data_bits_t bits;
  10. }

在C++中结构体是可以继承的,我们可以看到objc_class继承自objc_object结构体。
由于objc_object中只有一个isa指针,那么objc_class的结构体简化后如下:

  1. struct objc_class : objc_object {
  2. // Class ISA;
  3. Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  4. Class superclass;
  5. cache_t cache; // formerly cache pointer and vtable
  6. class_data_bits_t bits;
  7. }

Class _Nonnull isa:当前类对象的isa指针
Class superclass:父类
cache_t cache:类的缓存信息(后续会讲到)
class_data_bits_t bits:类的重要信息都存储在里面class_data_rw_t和class_data_ro_t中
class_data_rw_t:主要存储类的属性、实例方法、协议信息
class_data_ro_t:主要存储成员变量信息
综述:对象会被定义为objc_object结构体,类会被定义为objc_class对象,而objc_class继承自objc_objec所以类也是一个特殊的对象。

isa到底是什么

我们知道OC对象的本质其实就是结构体,OC对象对应的结构体就是struct objc_object,我们可以通过clang来讲我们的OC代码转换成C++代码,来求证一下。

Clang

  • Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
  • Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
  • Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是 C++的下一个小更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更 好的处理constexpr关键字。Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC。
  • 使用XCode自带 clang 可以将OC代码编译成C++代码
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m(源文件路径) -o main-arm64.cpp(目标文件路径)
  • Xcrun: Xcode Run
  • -sdk iphoneos: iOS系统
  • clang:XCode自带编译环境
  • -arch arm64:编译对应架构的成果物
  • -rewrite-objc:将文件便以为目标C++文件
  • -fobjc-arc:内存管理方式
  • -fobjc-runtime=ios-8.0.0:runtime版本 当需要用到runtime时需要使用该参数

    Clang转换

    ```objectivec @interface YJCar : NSObject

@property (copy, nonatomic) NSString *name;

  • (void) sayNB;

@end

@implementation YJCar

  • (void) sayNB {

}

@end

  1. 转换为C++的代码如下:
  2. ```objectivec
  3. #ifndef _REWRITER_typedef_YJCar
  4. #define _REWRITER_typedef_YJCar
  5. typedef struct objc_object YJCar;
  6. typedef struct {} _objc_exc_YJCar;
  7. #endif
  8. extern "C" unsigned long OBJC_IVAR_$_YJCar$_name;
  9. struct YJCar_IMPL {
  10. struct NSObject_IMPL NSObject_IVARS;
  11. NSString *__strong _name;
  12. };
  13. // @property (copy, nonatomic) NSString *name;
  14. // - (void) sayNB;
  15. /* @end */
  16. // @implementation YJCar
  17. static void _I_YJCar_sayNB(YJCar * self, SEL _cmd) {
  18. }
  19. static NSString * _I_YJCar_name(YJCar * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_YJCar$_name)); }
  20. extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
  21. static void _I_YJCar_setName_(YJCar * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct YJCar, _name), (id)name, 0, 1); }

我们可以看到YJCar被转换为了typedef struct objc_object YJCar,objc_object结构体。
我们在往下看struct NSObject_IMPL NSObject_IVARS,其实是isa指针

  1. struct NSObject_IMPL {
  2. __unsafe_unretained Class isa;
  3. };

联合体和位域

在探索ISA之前我们需要先了解一下联合体与联合体位域知识

联合体与结构体

联合体与结构体都是基础的构造数据类型

结构体(struct)

结构体: 把不同的数据整合成一个整体, 其变量是共存的, 变量是否使用都会为其开辟内存空间。

  • 优点: 储存容量大, 包容性强, 并且成员与成员之间不会互相影响
  • 缺点: 因为结构体里面成员都会给分配内存(不管用没用到), 没有用到的会造成内存浪费

    联合体(union)

    联合体: 也叫共用体, 把不同的数据整合成一个整体,其变量是互斥的, 所有成员同一片内存。联合体采用内存覆盖技术, 同一时刻只能保存一个成员值, 即:

  • 优点: 所有成员共用一段内存节省内存空间

  • 缺点: 包容性弱, 成员互斥, 同一时刻只能保存一个成员的值

    两者的区别
  • 内存占用

    • 结构体: 各个成员占用不同内存, 相互没有影响
    • 联合体: 各个内存占用同一内存, 修改一个成员影响其他所有成员
  • 内存分配

    • 结构体: 开辟的内存大于等于所有成员总和(对齐可能导致有空余)
    • 联合体: 内存占用为最大成员占用的内存

      ISA来了

  • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

  • 在arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
  • 需要将isa的内容&ISA_MASK位运算后得到具体的Class、Meta-Class的内存地址
    • 共用体类似结构体,共用体内的元素公用同一块内存空间
    • 共用体的内存大小至少为公用体内最大元素的内存空间大小
    • 共用体内可通过位域的方式为不同的元素分配制定的内存空间大小
    • 共用体内内存的排放为从后向前排
    • isa共用体结构
    • image.png
      • nonpointer:
        • 0:代表普通的指针,存储着Class、Meta-Class对象的内存地址
        • 1:代表优化过,使用位域存储更多的信息
      • has_assoc:
        • 是否有设置过关联对象,如果没有,释放时会更快
      • has_cxx_dtor:

是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

  • shiftcls:
    • 存储着Class、Meta-Class对象的内存地址信息
  • magic:
    • 用于在调试时分辨对象是否未完成初始化
  • weakly_referenced:
    • 是否有被指向或者曾经指向过ARC的若变量,如果没有,释放时会更快
  • deallocating:
    • 对象是否正在释放内存
  • extra_rc:
    • 里边存储的值时引用计数器减1
  • has_sidetable_rc:
    • 引用计数器是否过大无法存储在isa中
    • 如果为1,那么引用计数器会存储在一个叫SideTable的类的属性中

      Shiftcls在ARM和X86平台是有一定区别的

      ```objectivec

      if arm64

      // ARM64 simulators have a larger address space, so use the ARM64e // scheme even when simulators build for ARM64-not-e.

      if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR

      define ISA_MASK 0x007ffffffffffff8ULL

      define ISA_MAGIC_MASK 0x0000000000000001ULL

      define ISA_MAGIC_VALUE 0x0000000000000001ULL

      define ISA_HAS_CXX_DTOR_BIT 0

      define ISA_BITFIELD \

      uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t weakly_referenced : 1; \ uintptr_t shiftcls_and_sig : 52; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8

      define RC_ONE (1ULL<<56)

      define RC_HALF (1ULL<<7)

      else

      define ISA_MASK 0x0000000ffffffff8ULL

      define ISA_MAGIC_MASK 0x000003f000000001ULL

      define ISA_MAGIC_VALUE 0x000001a000000001ULL

      define ISA_HAS_CXX_DTOR_BIT 1

      define ISA_BITFIELD \

      uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /MACH_VM_MAX_ADDRESS 0x1000000000/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t unused : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19

      define RC_ONE (1ULL<<45)

      define RC_HALF (1ULL<<18)

      endif

elif x86_64

define ISA_MASK 0x00007ffffffffff8ULL

define ISA_MAGIC_MASK 0x001f800000000001ULL

define ISA_MAGIC_VALUE 0x001d800000000001ULL

define ISA_HAS_CXX_DTOR_BIT 1

define ISA_BITFIELD \

  1. uintptr_t nonpointer : 1; \
  2. uintptr_t has_assoc : 1; \
  3. uintptr_t has_cxx_dtor : 1; \
  4. uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
  5. uintptr_t magic : 6; \
  6. uintptr_t weakly_referenced : 1; \
  7. uintptr_t unused : 1; \
  8. uintptr_t has_sidetable_rc : 1; \
  9. uintptr_t extra_rc : 8

define RC_ONE (1ULL<<56)

define RC_HALF (1ULL<<7)

else

error unknown architecture for packed isa

endif

  1. 我们可以看到在ARM64平台占用的是3~36字节,共占33字节<br /> uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/<br />而在X86也就是我们的模拟上占用的是3~47字节,共占44字节<br /> uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
  2. <a name="NHNfj"></a>
  3. ### 通过Shitcls获取Mate元类
  4. 我们以X86平台为例
  5. <a name="fMIVk"></a>
  6. #### 方式一:位运算移动
  7. 其实我们要想获取Shitcls的信息,只需要将isa_t中的值向右移动3字节,在向左移动20字节,在向右移动17字节即可<br />我们可以通过位运算左移右移来处理<br />示例:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12834404/1631866083639-4fc36e53-3f95-4fa7-915f-ec2da3e38c57.png#clientId=u03b80edb-f027-4&from=paste&height=169&id=ue5fa923d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=380&originWidth=1800&originalType=binary&ratio=1&size=548032&status=done&style=none&taskId=u959d5927-ef1d-45e4-b571-7a3a4c134b0&width=800)<br />我们在继续查看一下元类的isa是什么<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12834404/1631866543343-2f7fce8a-43d5-4925-aa4d-60b00c7a264b.png#clientId=u03b80edb-f027-4&from=paste&height=215&id=ud260e62d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=348&originWidth=1296&originalType=binary&ratio=1&size=246437&status=done&style=none&taskId=u68243a7c-00a6-4bb9-8106-f8300cb511e&width=800)<br />方式二:与运算<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12834404/1631866628486-6ddde593-cf5a-4938-846a-1c45bf4c3bc0.png#clientId=u03b80edb-f027-4&from=paste&height=134&id=u35295bac&margin=%5Bobject%20Object%5D&name=image.png&originHeight=236&originWidth=1058&originalType=binary&ratio=1&size=142889&status=done&style=none&taskId=u49bf9d52-bc3d-4700-94e6-1e81e646d28&width=600)<br />通过上述lldb的方式分析来看,我们已经知道了isa的结构体,并且也已经了解到了,实例对象的isa指向关系。<br />总结:<br />实例对象isa-->类对象isa-->元类isa-->根原类isa-->根原类isa-->根原类isa
  8. <a name="hqK7X"></a>
  9. # isa和实例对象关系的建立
  10. 在上一节我们分析alloc的过程,在createInstanceFromZone函数中,通过initIsa函数将我们创建的objc对象与类进行关联。<br />我们先来看一下initIsa的结构体代码
  11. ```objectivec
  12. objc_object::initIsa(Class cls)
  13. {
  14. initIsa(cls, false, false);
  15. }

我们发现结构内部调用了initIsa()方法
image.png

SuperClass

superclass存储的就是当前类的的父类信息,实例对象没有supperclass,只有类对象才有superclass。这也就是objc_object结构体只有isa的原因。
总结:
Class对象SupperClass—>父Class—>父Class—>……—>NSobject—>nil

方法调用流程

方法调用分为实例方法调用个类方法调用,在研究调用之前我们先了解一下这心方法的存储位置。

instance对象(实例对象)

image.png

  • instance对象的isa指向class
  • 当调用对象方法时,先通过isa找到class对象,然后在找到对象方法进行调用
  • class的isa指向元类的class,也就是mate_class
  • 当调用类方法时,需要通过isa找到class,在通过class的isa找到元类,然后在找到类方法进行调用

    class对象(类对象)

    image.png

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

    mate_class对象(元类对象)

    image.png

  • 当Student的class对象要调用Person的对象方法时,会先通过Student的isa找到Student的mate_class,在通过Student的mate_class的superclass找到Person的mate_class,最后找到Person的类方法进行调用

  • 此处特殊:当通过superclass查找父类的方法时,一直找到NSObject的时候都没找到方法,系统会想前查找一次,也就是会找到根元类的类对象的实例方法,如果找到了也会进行调用,在找不到就会报错。

    最后我们在来看isa和superclass的大神图,就全都一目了然了

    万能图

    image.png

    KC老师拆分图

image.png
image.png