Runtime-Class底层结构

204 阅读4分钟

isa详解

  • 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
  • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
  • 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息,具体结构如下
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
        ISA_BITFIELD;  // defined in isa.h 下面是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 deallocating      : 1;                                       
        uintptr_t has_sidetable_rc  : 1;                                       
        uintptr_t extra_rc          : 19
    };
};

isa详解-位域

  • 位域(又叫做位段)其实是一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。有些信息在存储的时候,并不需要占用一个完整字节,有时候只需要占用一个或几个二进制位,比如存放一个BOOL类型的变量时,只需要保存0或1两种状态,此时只需要1个二进制位就能存储。因此,位域就是运用在这种场景下的一种数据结构,使用位域可以有效的节省存储空间。
  • 位域可以把一个字节中的二进制位划分为几个不同的区域,并且制定每个区域占用的位数,每个域可以设置一个域名,可以根据域名对指定的位进行操作。
  • 但是位域也有明显的缺点,就是它的内存分配和内存对齐的方式依赖于具体的机器和操作系统,不同的平台可能会有不同的结果。
  • 位域的结构和结构体类似,它的形式为
struct 位域结构名称{
    类型说明符 位域名 : 位域长度;
    类型说明符 位域名 : 位域长度;
    类型说明符 位域名 : 位域长度;
    ......
}

isa的结构

  • isa_t作为共用体,内部使用8个字节的内存空间,共64位二进制位,存放了以下信息:
  • nonpointer(1位)
    0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
    1,代表优化过,使用位域存储更多的信息
  • has_assoc(1位)
    是否有设置过关联对象,如果没有,释放时会更快
  • has_cxx_dtor(1位)
    是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
  • shiftcls(33位)
    存储着Class、Meta-Class对象的内存地址信息
  • magic(6位)
    用于在调试时分辨对象是否未完成初始化
  • weakly_referenced(1位)
    是否有被弱引用指向过,如果没有,释放时会更快
  • deallocating(1位)
    对象是否正在释放
  • extra_rc(1位)
    里面存储的值是引用计数器减1
  • has_sidetable_rc(19位)
    引用计数器是否过大无法存储在isa中
    如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

class的结构

  • 用一张图来展示就是:

class-rw-t

  • class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容

class-ro-t

  • class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

method_t

  • method_t是对方法\函数的封装
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
}
  • IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
  • SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
    可以通过@selector()和sel_registerName()获得
    可以通过sel_getName()和NSStringFromSelector()转成字符串
    不同类中相同名字的方法,所对应的方法选择器是相同的
typedef struct objc_selector *SEL;
  • types包含了函数返回值、参数编码的字符串

Type Encoding

  • iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码.
codemeaning
cchar
iint
sshort
llong
qlong long
cunsigned char
Iunsigned int
Sunsigned short
Lunsigned long
Qunsigned long long
ffloat
ddouble
BC++ bool or C99 _Bool
vvoid
*A character string(char *)
@An object(whether statically typed or typed id)
#class object(Class)
:method selecter(SEL)
[array type]An Array
{name=type...}A structure
{name=type...}A union
bnumA bit field of num bits
^typeA pointer to type
?An unknown type

方法缓存cache_t

  • Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
  • 缓存查找
objc-cache.mm
struct bucket_t *cache_t::buckets()