OC 对象、位域、isa

一、对象的本质

1.1 clang

1.1.1clang 概述

Clang是一个C语言C++Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。

Clang是一个由Apple主导编写,基于LLVMC/C++/Objective-C编译器。
它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过__attribute__((overloadable))来修饰函数),其目标(之一)就是超越GCC

1.1.2 clang与xcrun命令

1.1.2.1 clang

把目标文件编译成c++文件,最简单的方式:

clang -rewrite-objc main.m -o main.cpp

如果包含其它SDK,比如UIKit则需要指定isysroot

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m -o main.cpp

更多clang命令参考

  • 如果找不到Foundation则需要排查clang版本设置是否正确。使用which clang可以直接查看路径。有些公司会使用clang-format来进行代码格式化,需要排查环境变量中是否导出了相关路径(如果导出先屏蔽掉)。正常路径为/usr/bin/clang
  • isysroot也可以导出环境变量进行配置方便使用。

1.1.2.2 xcrun(推荐)

xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,比clang更好用。
模拟器命令:

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64simulator.cpp

真机命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

1.2 对象c++代码分析

main.m文件如下,直接生成对应的.cpp文件对HotpotCat进行分析。

#import 

@interface HotpotCat : NSObject

@end

@implementation HotpotCat

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}

由于clang生成的文件过大,推荐使用xcrun生成.cpp文件进行分析。

上面的代码使用clang最简单的方式生成的大小为4.4MB 11万行,而用xcrun生成的代码大小1.7MB 3.4万行

1.2.1 对象在底层是结构体

直接搜索HotpotCat有如下代码:

image.png

可以看到生成了HotpotCat_IMPL是一个结构体,那么HotpotCat_IMPL就是HotpotCat的底层实现么?对HotpotCat增加属性hp_name

@property(nonatomic, copy) NSString *hp_name;

重新生成.cpp文件:

image.png

这也就验证了HotpotCat_IMPL就是HotpotCat的底层实现,那么说明: 对象在底层的本质就是结构体

HotpotCat_IMPL结构体中又嵌套了NSObject_IMPL结构体,这可以理解为继承。
NSObject_IMPL定义如下:

struct NSObject_IMPL {
    Class isa;
};

所以NSObject_IVARS就是成员变量isa

1.2.2 objc_object & objc_class

HotpotCat_IMPL上面有如下代码:

typedef struct objc_object HotpotCat;

为什么HotpotCatobjc_object类型?这是因为NSObject的底层实现就是objc_object

同样的Class定义如下:

typedef struct objc_class *Class;

objc_class类型的结构体指针。

同样可以看到idobjc_object结构体类型指针。

typedef struct objc_object *id;

这也就是id声明的时候不需要*的原因。

1.2.3 setter & getter

.cpp文件中有以下代码:


// @property(nonatomic, copy) NSString *hp_name;

/* @end */


// @implementation HotpotCat
 
//这里是setter和getter  参数self _cmd 隐藏参数
static NSString * _I_HotpotCat_hp_name(HotpotCat * self, SEL _cmd) {
    //return  self + 成员变量偏移
    return (*(NSString **)((char *)self + OBJC_IVAR_$_HotpotCat$_hp_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_HotpotCat_setHp_name_(HotpotCat * self, SEL _cmd, NSString *hp_name) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct HotpotCat, _hp_name), (id)hp_name, 0, 1);
}
// @end
  • 根据系统默认注释和函数名称确认这里是hp_namesettergetter方法。
  • getter方法返回hp_name是通过self + 成员变量偏移获取的。getter同理。

二、位域

struct Direction {
    BOOL left;
    BOOL right;
    BOOL front;
    BOOL back;
};

上面是一个记录方向的结构体。这个结构体占用4字节32位:00000000 00000000 00000000 00000000。但是对于BOOL值只有两种情况YES/NO。那么如果能用40000来代替前后左右,就只需要0.5个字节就能表示这个数据结构了(虽然只需要0.5字节,但是数据单元最小为1字节)。那么Direction的实现显然浪费了3倍的空间。有什么优化方式呢?位域

2.1 结构体位域

修改DirectionHPDirection:

struct HPDirection {
    BOOL left : 1;
    BOOL right : 1;
    BOOL front : 1;
    BOOL back : 1;
};

格式为:数据类型 位域名称:位域长度

验证:

struct Direction dir;
dir.left = YES;
dir.right = YES;
dir.front = YES;
dir.back = YES;
struct HPDirection hpDir;
hpDir.left = YES;
hpDir.right = YES;
hpDir.front = YES;
hpDir.back = YES;
printf("\nDirection size:%zu\nHPDirection size:%zu\n",sizeof(dir),sizeof(hpDir));
image.png
  • dir分别存储在4个字节中,并且只占了1位。
  • hpDir存储在1个字节中,占用了4位。
  • dir占用4字节内存,hpDir占用1字节。
位域优化前后对比

2.2 联合体

2.2.1结构体&联合体对比

//结构体联合体对比
//共存
struct HPStruct {
    char *name;
    int age;
    double height;
};

//互斥
union HPUnion {
    char *name;
    int age;
    double height;
};


void testStructAndUnion() {
    struct HPStruct s;
    union HPUnion u;
    s.name = "HotpotCat";
    u.name = "HotpotCat";
    s.age = 18;
    u.age = 18;
    s.height = 180.0;
    u.height = 180.0;
}

分别定义了HPStruct结构体和HPUnion共用体,在整个赋值过程中变化如下:

image.png

总结:

  • 结构体(struct)中所有变量是“共存”的。
    优点:“有容乃大”, 全面;
    缺点:struct内存空间的分配是粗放的,不管用不用全分配。
  • 联合体/共用体(union)中是各变量是“互斥”的。
    缺点:不够“包容”;
    优点:内存使用更为精细灵活,节省了内存空间。
  • 联合体在未进行赋值前数据成员会存在脏数据。

2.2.2 联合体位域

HPDirectionItem.h

@interface HPDirectionItem : NSObject

@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;

@end

HPDirectionItem.m:

#define HPDirectionLeftMask   (1 << 0)
#define HPDirectionRightMask  (1 << 1)
#define HPDirectionFrontMask  (1 << 2)
#define HPDirectionBackMask   (1 << 3)

#import "HPDirectionItem.h"

@interface HPDirectionItem () {
    //这里bits和struct用任一一个就可以,结构体相当于是对bits的解释。因为是共用体用同一块内存。
    union {
        char bits;
        //位域,这里是匿名结构体(anonymous struct)
        struct {
            char left  : 1;
            char right : 1;
            char front : 1;
            char back  : 1;
        };
    }_direction;
}

@end

@implementation HPDirectionItem

- (instancetype)init {
    self = [super init];
    if (self) {
        _direction.bits = 0b00000000;
    }
    return self;
}

- (void)setLeft:(BOOL)left {
    if (left) {
        _direction.bits |= HPDirectionLeftMask;
    } else {
        _direction.bits &= ~HPDirectionLeftMask;
    }
}

- (BOOL)left {
    return _direction.bits & HPDirectionLeftMask;
}
//……
//其它方向设置同理
//……
@end
  • HPDirectionItem是一个方向类,类中有一个_direction的共用体。
  • _direction中有bitsanonymous struct,这里anonymous struct相当于是对bits的一个解释(因为是共用体,同一个字节内存。下面的调试截图很好的证明力这一点)。
  • 通过对bits位移操作来进行数据的存储,其实就相当于对结构体位域的操作。连这个可以互相操作。

所以可以将settergetter通过结构体去操作,效果和操作bits相同:

- (void)setLeft:(BOOL)left {
    _direction.left = left;
}

- (BOOL)left {
    return _direction.left;
}

当然也可以两者混用:

- (void)setLeft:(BOOL)left {
    _direction.left = left;
}

- (BOOL)left {
    return _direction.bits & HPDirectionLeftMask;
}

根本上还是对同一块内存空间进行操作。

调用:

void testUnionBits() {
    HPDirectionItem *item = [HPDirectionItem alloc];
    item.left = 1;
    item.right = 1;
    item.front = 1;
    item.back = 1;
    item.right = 0;
    item.back = 0;
    NSLog(@"testUnionBits");
}
联合体位域断点跟踪对比

这样整个赋值流程就符合预期满足需求了。

  • 联合体位域作用:优化内存空间和访问速度。

详细内容见:BitsTest Demo

三、 isa

alloc分析的文章中已经了解到执行完initIsa后将alloc开辟的内存与类进行了关联。在initIsa中首先创建了isa_t也就是isa,去掉方法后它的主要结构如下:

union isa_t {
    //……
    uintptr_t bits;
private:
    Class cls;
public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
    //……
#endif
    //……
};

它是一个union,包含了bitscls(私有)和一个匿名结构体,所以这3个其实是一个内容,不同表现形式罢了。这个结构似曾相识,与2.2.2中联合体位域一样。不同的是isa_t占用8字节64位。

没有关联类时isa分布(默认都是0,没有指向):

image.png

关联isa后:

image.png

bitscls分析起来比较困难,既然三者一样,那么isa_t的核心就是ISA_BITFIELD

3.1 isa结构分析

ISA_BITFIELD宏定义展开结构图下。
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                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

arm64

#     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)

根据上面的定义isa位域分布如下图:

isa位域分布示意图

  • nonpointer:表示是否对 isa 指针开启指针优化。 0表示纯isa指针,1表示不止是类对象地址,isa中包含了类信息、对象的引用计数等。也就是使用位域保存更多信息。
  • has_assoc:关联对象标志位,0不存在1存在。
  • has_cxx_dtor:该对象是否有C++/Objc的析构器,如果有析构函数,则需要做析构逻辑; 如果没有,则可以更快的释放对象。
  • shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有33(3~35) 位用来存储类指针。存放着 ClassMeta-Class 对象的内存地址。
  • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
  • weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存。
  • has_sidetable_rc:散列表。是否需要使用 sidetable 来存储引用计数。少量的引用计数是不会直接存放在 SideTables 表中,与extra_rc有关。
  • extra_rc:表示该对象的引用计数值,实际上是引用计数值减 1。 当extra_rc被存满时引用计数会存入SideTables中,SideTables 中有很多张 SideTable,每个 SideTable 也都是一个散列表,而引用计数表就包含在 SideTable 之中。在这里x86架构下最大为28arm64下为219。例如: extra_rc:3 如果对象的引用计数为 8,那么 extra_rc7。如果引用计数大于 8, 则需要使用到 has_sidetable_rc

isa就是一个类的指针,但是指针用不了64位。并且每一个类都有isa。所以通过位域来存储优化内存和访问速度。将与类相关的一些内容存入了isa中。这个时候isa就不是一个简单的地址了。

3.1.1 nonpointer配置

那么什么情况下nonpointer会为0isa为一个纯指针呢?在_class_createInstanceFromZone方法中有如下代码:

bool fast = cls->canAllocNonpointer();
if (!zone && fast) {
    obj->initInstanceIsa(cls, hasCxxDtor);
} else {
    obj->initIsa(cls);
}

可以看到核心逻辑是canAllocNonpointer

#define FAST_CACHE_REQUIRES_RAW_ISA   (1<<13)

bool canAllocNonpointer() {
    ASSERT(!isFuture());
    return !instancesRequireRawIsa();
}

bool instancesRequireRawIsa() {
    return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA);
}

可以看到是取的缓存的标志位,并没有特别有用的信息。那么就在cache.setBit打个条件断点:

image.png

最终找到了调用的地方是realizeClassWithoutSwift方法:

image.png

既然nonpointer1的时候走的是if分支,那么为0肯定就走else分支了。这个时候找到了DisableNonpointerIsa,点进去可以看到定义如下:
image.png

看到objc-env就想到了环境变量。那么配置OBJC_DISABLE_NONPOINTER_ISAYES(配置1无效)。

配置nonpointer环境变量:

NONPOINTER_ISA配置

验证nonpointer

NONPOINTER_ISA验证

这个时候isa就是一个纯指针了。这里也可以看出纯指针浪费了很多空间,根本用不到64位。

  • 通过配置OBJC_DISABLE_NONPOINTER_ISAYES可以设置nonpointer0
  • 更多环境变量配置可以在objc源码objc-env.h文件中查看。

3.2 通过isa获取cls

3.2.1 通过mask验证

既然isa与类关联后就有了类的信息,那么是怎么关联的呢?
首先要清楚MASK的概念,MASK翻译过来就是面具,其实它的作用也就是面具。比如戴面具要漏出眼睛,戴同样的面具漏出的部位是一样的,戴多层面具漏出的部位也是一样的。MASK的作用就相当于去除不必要的位域数据,取到需要的位域数据。

isa获取cls

  • isa & isa_mask -> clscls是通过 掩码maskisa关联的。
  • cls & isa_mask -> cls:多次mask值不变。

0x00007ffffffffff8就是二进制0b0000000000000000011111111111111111111111111111111111111111111000&操作就相当于保留中间44位,其余清零。

3.2.2 通过位域结构验证

上面已经清楚了isa的位域分布结构。cls就是shiftcls。对于x86_64来说shiftcls占用44位,前后分别3(nonpointer + has_assoc + has_cxx_dtor)位和17(magic + weakly_referenced + unused + has_sidetable_rc + extra_rc)位。那么我们可以通过位移获取到shiftcls

位移的过程中就相当于是擦除无用信息,只保留shiftcls。变化过程如下:

isa位移变化过程图

isa位移调试
  • isa >> 3清空nonpointer + has_assoc + has_cxx_dtor
  • isa << 20清空magic + weakly_referenced + unused + has_sidetable_rc + extra_rc以及右移的3位。
  • isa >> 17位归位得到cls值。

3.2.2 通过isa_t验证

除了上面通过位移获取cls,还有一种更简单的方式。既然isa在底层是isa_t结构那么可以直接转成结构体获取shiftcls

isa_t获取cls

  • isa强转isa_t获取shiftcls值。
  • shiftcls末尾补000得到cls地址。

3.3 代码验证isa与cls

既然类的本质是结构体,那么模仿结构体就能和对象互相转换了。模拟isaclass的代码如下:

#define ISA_MASK   0x00007ffffffffff8ULL
//模拟isa
union isa_t {
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44;
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t unused            : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
    };
};

//模拟class
struct HPClass {
    union isa_t isa;
};

转换测试:

NSObject *obj = [NSObject alloc];
struct HPClass *p = (__bridge void *)obj;
NSLog(@"nonponinter ISA:0x%lx",p->isa.bits);
NSLog(@"nonponinter:0x%x",p->isa.nonpointer);
NSLog(@"has_assoc:0x%x",p->isa.has_assoc);
NSLog(@"has_cxx_dtor:0x%x",p->isa.has_cxx_dtor);
NSLog(@"shiftcls:0x%lx",p->isa.shiftcls);
NSLog(@"cls(p->isa.bits):0x%llx == NSObject.class:0x%llx",p->isa.bits & ISA_MASK,NSObject.class);
NSLog(@"magic:0x%x",p->isa.magic);
NSLog(@"weakly_referenced:0x%x",p->isa.weakly_referenced);
NSLog(@"unused:0x%x",p->isa.unused);
NSLog(@"has_sidetable_rc:0x%x",p->isa.has_sidetable_rc);
NSLog(@"extra_rc:0x%x",p->isa.extra_rc);

结果:

nonponinter ISA:0x1dffff96af9119
nonponinter:0x1
has_assoc:0x0
has_cxx_dtor:0x0
shiftcls:0xffff2d5f223
cls(p->isa.bits):0x7fff96af9118 == NSObject.class:0x7fff96af9118
magic:0x3b
weakly_referenced:0x0
unused:0x0
has_sidetable_rc:0x0
extra_rc:0x0
Hello, World!

在这里对象完全转换成了结构体,再次说明对象本质就是结构体。p->isa.bitsNSObject.class相同都指向cls。所以通过系统API获取到的isa就是clsclass方法底层对bits进行了& MASK操作,与这里的模拟代码相同)。

四、 initInstanceIsa源码分析

4.1 initIsa

isa和类绑定最终是调用的initIsa方法,核心源码如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{     
    isa_t newisa(0);
    if (!nonpointer) {//纯指针
        newisa.setClass(cls, this);
    } else {//nonpointer指针
#if SUPPORT_INDEXED_ISA //表示在 armv7k or arm64_32架构下
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        //64位
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

从源码中可以看到纯指针的情况下直接调用setClass进行赋值了,32位架构的情况下也是能开启nonpointer的,cls->classArrayIndex赋值给了indexcls64位的情况下也调用了setClass。既然nonpointer无论在什么情况下都调用了setClass(这里除了32位),那么就说明区分逻辑在setClass方法中。

  • 设置bits就相当于设置默认值(x86_64下值为0x001d800000000001),由于是共用体所以要放在最前面设置。

对应数据如下:

image.png

相当于nonpointer设置为1magic0b111011说明已经初始化了。magic的具体含义先不在这里分析。

  • has_cxx_dtor是传递进来的通过cls->hasCxxDtor()获取的。
  • indexcls相当于关联cls32位)。
  • setClass相当于关联cls64位)。
  • extra_rc引用计数初始化为1

那么核心的关联逻辑就是setClasscls->classArrayIndex了。

4.2 setClass

setClass源码如下如下:

inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR //arm64e/模拟器
    //根据isa mode 进行标记操作
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    //nonpointer 为 0 直接赋值
    uintptr_t signedCls = (uintptr_t)newCls;
    //仅仅针对swift关联
#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    //直接赋值
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls->isSwiftStable())
        //isSwiftStable表示该类是否是稳定的 Swift ABI 的 Swift 类
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    //这里与newCls->isSwiftStable()逻辑相同了
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   else
#       error Unknown isa signing mode.
#   endif
    //右移3位 shiftcls_and_sig 相当于是 shiftcls 不同架构下叫法不同。位数不同。
    shiftcls_and_sig = signedCls >> 3;

#elif SUPPORT_INDEXED_ISA //32位 armv7k or arm64_32
    //32位下直接赋值,这里对应 nonpointer 为 0 的情况,为1的情况在外层。
    cls = newCls;

#else // Nonpointer isa, no ptrauth
    //x86_64 44位 nonpointer 是否开启都走这里
    //右移3位。这里右移3位所以还原的时候需要左移3位 赋值给shiftcls
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}
  • arm64e/模拟器下会根据ISA_SIGNING_SIGN_MODE做判断和操作,最终cls >> 3赋值给shiftcls_and_sig
  • 32为架构下并且nonpointer0的时候直接赋值给isa_tcls
  • 64位下直接>>3位无论nonpointer为何值。

4.3 cls->classArrayIndex()

classArrayIndex
源码如下:

    unsigned classArrayIndex() {
            return bits.classArrayIndex();
    }

    unsigned classArrayIndex() {
#if SUPPORT_INDEXED_ISA
        return data()->index;
#else
        return 0;
#endif
    }

可以看到取的是data()->index

setClassArrayIndex
逻辑如下:

void setClassArrayIndex(unsigned Idx) {
        // 0 is unused as then we can rely on zero-initialisation from calloc.
        ASSERT(Idx > 0);
        data()->index = Idx;
}

调用者是objc_class::chooseClassArrayIndex

chooseClassArrayIndex

void objc_class::chooseClassArrayIndex()
{
#if SUPPORT_INDEXED_ISA
    Class cls = (Class)this;
    runtimeLock.assertLocked();

    if (objc_indexed_classes_count >= ISA_INDEX_COUNT) {
        // No more indexes available.
        ASSERT(cls->classArrayIndex() == 0);
        cls->setInstancesRequireRawIsaRecursively(false/*not inherited*/);
        return;
    }

    unsigned index = objc_indexed_classes_count++;
    if (index == 0) index = objc_indexed_classes_count++;  // index 0 is unused
    classForIndex(index) = cls;
    cls->setClassArrayIndex(index);
#endif
}

可以看到index来自objc_indexed_classes_countchooseClassArrayIndex是在_read_images的时候调用的。

objc_indexed_classes_count
objc_indexed_classes_count的声明在objc-gdb.h中:

// When we don't have enough bits to store a class*, we can instead store an
// index in to this array.  Classes are added here when they are realized.
// Note, an index of 0 is illegal.
OBJC_EXPORT uintptr_t objc_indexed_classes_count;

大概意思是类被加载的时候回存入数据,这个值就是记录数组数量的。通过objc_indexed_classes_count就能在数组中找到类(按image分组)。
所以indexcls存储的是类的索引

4.4 isa关联类流程

isa关联类流程图

isa关联cls总结

  • arm64e/模拟器
    • 1.根据ISA_SIGNING_SIGN_MODE操作cls
    • 2.shiftcls_and_sig = signedCls >> 3,右移3位赋值给shiftcls_and_sig
  • 64位:shiftcls = (uintptr_t)newCls >> 3,右移3位赋值给shiftcls
  • 32位:
    • nonpointer 为假:cls = newCls直接赋值给cls
    • nonpointer 为真:indexcls = (uintptr_t)cls->classArrayIndex()记录index

⚠️64位下无论开不开启nonpointercls都会右移3位进行赋值。

BitsTest Demo

你可能感兴趣的:(OC 对象、位域、isa)