03 - isa结构探索及关联类

今天的主题是探索isa的结构 在此之前我们需要先了解下什么是联合体

构造数据类型的方式有以下两种 :

  • 结构体 (struct)
  • 联合体 (union ,也称为共用体)

结构体

结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存。

  • 缺点:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费

  • 优点:存储容量较大包容性强,且成员之间不会相互影响

联合体

联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖

  • 缺点:,包容性弱

  • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间

两者的区别

  • 内存占用情况

  • 结构体的各个成员会占用不同的内存,互相之间没有影响

  • 共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员

  • 内存分配大小

  • 结构体内存 >= 所有成员占用的内存总和(成员之间可能会有缝隙)

  • 共用体占用的内存等于最大的成员占用的内存

现在我们已经大致了解什么是联合体,下面进行isa探索,上代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        
        Person *objc = [Person alloc];

        NSLog(@"Hello, World!  %@",objc);
    }
    return 0;
}

alloc的源码跟进去到关联指针和类的步骤:

if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

跟进obj->initInstanceIsa(cls, hasCxxDtor);

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

主要做的事情是initIsa(cls, true, hasCxxDtor);

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls); // isa 初始化
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);  // isa 初始化
#if SUPPORT_INDEXED_ISA // !nopointmenter 执行的流程 ,即isa通过cls定义
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else //bits 时执行的流程
        newisa.bits = ISA_MAGIC_VALUE;//bist进行赋值  为0x001d800000000001ULL
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

可以看到不管!nonpointer条件是否满足,都会生成一个isa_t的类型。跟进去可以发现:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

isa_t类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能

isa_t的定义中可以看出:

提供了两个成员,clsbits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,有两种初始化方式

通过cls初始化,bits无默认值

通过bits初始化,cls有默认值

还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 __arm64__(对应ios 移动端) 和__x86_64__(对应macOS),以下是它们的一些宏定义,如下图所

位域宏定义

其中存储的成员信息:

  • nonpointer一般自定义的类都是这个类型的,而系统类才会有纯isa指针的情况,占1位。
    0:纯isa指针。
    1:不只是类对象地址,isa中包含了类信息、对象的引用计数等。
  • has_assoc关联对象标志位,0代表没有关联对象,1代表存在关联对象,占1位。
  • has_cxx_dtor该对象是否有C++或OC的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快释放对象,占1位。
  • shiftclx存储类指针的值, 也就是类信息,开启指针优化的情况下,在arm64架构中有33位用来存储类指针,`x86_64``架构中占44位。
  • magic 用于调试器判断当前对象是真的对象还是没有初始化的空间,占6位。
  • weakly_refrenced是指对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
  • deallocating 标志对象是否正在释放内存。
  • has_sidetable_rc当对象引用计数大于2的19次方(x86_64架构为2的8次方)时,则需要存储到散列表,这时该变量值变为true
  • extra_rc表示该对象的``引用计数值,最大为2的19次方(x86_64架构为2的8次方),实际上是引用计数值减1,,如果大于最大容量,就需要取一半计数存到散列表中,真机上最多有8张散列表存储对象引用计数,x86_64则最多64张,这时上面的has_sidetable_rc值置为true。

针对两种不同平台,其isa的存储情况如图所示

bits位域分布

下面验证alloc过程中isa是如何跟进行关联的

上面 initIsa方法的源码实现我们可以发现逻辑主要分为两部分

  • 通过 cls 初始化 isa
  • 通过 bits 初始化 isa

运行源码 断点如图 打印newisa信息

调试结果

-继续执行,到newisa.bits = ISA_MAGIC_VALUE;下一行,表示为isa的bits成员赋值,重新执行lldb命令p newisa,得到的结果如下

调试结果

对比发现数据发生变化,可以看到赋值后的nonpointer已经是1了,magic为59。

  • 其中magic59是由于将isa指针地址转换为二进制,从47(因为前面有4个位域,共占用47位,地址是从0开始)位开始读取6位,再转换为十进制,如下图所示
计算器结果

此时此刻,可以得出结果,这magic的59确实是由0x001d800000000001ULL填进去的。

继续执行 过newisa.shiftcls = (uintptr_t)cls >> 3;这一步 打印newisa

赋值结果
  • shiftcls0变成了536871965即为 (uintptr_t)cls >> 3
为什么在shiftcls赋值时需要类型强转

因为内存的存储不能存储字符串,机器码只能识别0 、1这两种数字,所以需要将其转换为uintptr_t数据类型,这样shiftcls中存储的类信息才能被机器码理解, 其中uintptr_t是long

为什么需要右移3位

主要是由于shiftcls处于isa指针地址的中间部分,前面还有3个位域,为了不影响前面的3位域的数据,需要右移将其抹零

进一步验证类与指针对象相关联

位运算流程图
    1. 控制台打印这个obj指针,并将isa的内存地址右移3位,再左移20位,再右移17位,这时再打印地址移动之后的isa的地址,可以看到,就是我们上面关联的类,也就是说这时对象指针和类关联上了, 如图
    1. 通过isa & ISA_MSAK 如图
位运算验证结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342