03 isa探究

iOS开发者一定知道每个实例对象都有一个isa指针,其中存储着对象的类信息。今天我们就来探究下isa是如何保存类的信息的。
通过objc的源码可以找到我们在调用alloc方法创建实例对象的时候有初始化isa的操作,其初始化代码如下

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        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
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

由上述代码可知,isa其实是isa_t类型的结构体,那么isa_t又是什么呢?

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

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

从isa_t的定义中引出了一个新的数据类型--联合体。我们来看下联合体和结构体有什么区别:

结构体

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

  • 优点存储容量大,包容性强,且成员之间不会相互影响
  • 缺点:所有变量都会分配内存,内存消耗较大
联合体

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

  • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间
  • 缺点包容性差
    我们再回过来看isa_t的定义,其定义了两个变量一个为bits,另一个为cls,同时如果定义了ISA_BITFIELD的情况下,定义了位域结构体以便我们精确的操作isa_t中的不同位的数据。ISA_BITFIELD的定义根据不同的cpu进行了不同的定义,定义的字段和内容大同小异,只是不同的字段占用的位数量不一样,本文仅以x86_64为例说明:
      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

如上所示就是ISA_BITFIELD在x86_64下的定义,其中:

  • nonpointer:存储于第0位,标识是否为优化isa标志。0代表不是优化的isa,此isa是一个纯指向类或者原类的指针;1标识是优化的isa,此isa中包含类信息,对象的引用计数等。我们现在创建的对象中的isa基本上都是优化后的isa

  • has_assoc:存储于第1位,关联对象标志位。对象含有或者曾经含有关联引用。0表示没有;1表示有。没有关联引用的对象可以更快的释放内存。

  • has_cxx_dtor:存储于第3位,析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速的释放对象。

  • shiftcls:存储于第3-46位,存储类的信息,这个是我们主要关注的内容

  • magic:存储于第47-52位,判断对象是否初始化完成,是调试器判断当前对象是真的对象还是没有初始化的空间。

  • weakly_referenced:存储于第53位,标识对象是否被指向或者曾经被指向一个ARC的弱引用变量。如果没有弱引用的对象可以更快的被释放。

  • unused:存储于第54位。

  • has_sidetable_rc:存储于第55位,判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

  • extra_rc:存储于第56-63位,存放该对象引用计数值-1的结果

    ISA_BITFIELD

了解了isa的数据类型之后我们看下objc是如何设置isa的呢?根据objc源码我们找到了设置isa的具体方法initIsa,代码如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        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
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    isa = newisa;
}

在实际开发中,我们创建的类基本上都是nonpointer的,故此处isa的创建会走else中的代码,在此处打上断点后我们一步一步跟断点。


ISA_MAGIC_VALUE

经过ISA_MAGIC_VALUE初始化newisa.bit之后,我们看到newisa中bits的值变为了8303511812964353,而ISA_MAGIC_VALUE转为10进制数也是8303511812964353,在计算器中输入后可以看到2进制计数法中每一位的情况,如下所示

ISA_MAGIC_VALUE

newisa的位信息中,除了nonpointer为1与计算器中的第0位的1相符之外,magic被置为了59,同时计算器中从第47位开始(即magic的首位)显示为110111,此时我们将59输入计算器中发现59的二进制计数正是110111,与bit中的值相等

59

下一步是setClass,我们进入setClass方法后发现设置shiftcls的代码就一行。

    shiftcls = (uintptr_t)newCls >> 3;

此处将class右移了3位存入了shiftcls中,这是因为class信息的最后4位始终是0,即无效位,为了匹配shiftcls是从第3位开始存储的,便去除了最右边三位无效位存入了shiftcls中

setClass

在setClass之后shiftcls的信息变成了536875037,那么此时shiftcls中存储的是否是SLPerson的类信息呢?
我们通过以下几种方式来看下:

  1. 我们刚刚说了class的最后三位是0,所以我们只需要将isa中除了shiftcls的其它位置0便可以得到正确的类信息,即我们可以采取右移3位,左移20位,再右移17位的方式将其余位全部置0。
(lldb) p newisa.bits >> 3
(uintptr_t) $53 = 1037939513495581
(lldb) p $53 << 20
(uintptr_t) $54 = 562954278797312
(lldb) p $54 >> 17
(uintptr_t) $55 = 4295000296
(lldb) po $55
SLPerson
运算过程

通过如上打印可以看到我们将其余位全部置0后得到的信息就是SLPerson。
这是我们直接通过位运算来还原了isa中类的信息,那么系统又是如何获取类的信息的呢?

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

inline Class
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}

inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
    if (nonpointer) {
        return classForIndex(indexcls);
    }
    return (Class)cls;
#else
    return getClass(authenticated);
#endif
}

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;
    clsbits &= ISA_MASK;

    return (Class)clsbits;
#endif
}

#   define ISA_MASK        0x00007ffffffffff8ULL

从源码可知,获取class的方法最终就是一行代码return bits & ISA_MASK,我们将ISA_MASK放入计算器中发现就是一个3-46位为1,其余位为0的二进制数。与bits按位进行&运算最终的结果和我们进行位移运算是一样的~

ISA_MASK

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

推荐阅读更多精彩内容