iOS底层isa探索分析

946 阅读5分钟

点击获取更多资料

1. 概述

说起isa,我想对于一些探索过底层的朋友,应该并不陌生,而对于一些没有探究过的朋友,就有些云里雾里了,这是什么东西,干什么用的,平时开发中也没用到啊?

我们平时开发确实没有直接用到isa,但是它确实时时刻刻地在底层发挥它的作用,那么既然它有用,那它是长什么样呢,又是怎么运作的呢?本篇文章,我们一探究竟。

2. isa结构

首先我们先看一下isa的结构,如下:

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是一个union,里面有个uintptr_t,其定义如下:

typedef unsigned long           uintptr_t;

uintptr_t其实就是一个long类型,占8节。

还有个struct,struct里面有个宏定义ISA_BITFIELD,我们在具体看一下:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   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 deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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 deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

由上面的宏定义可知,针对不同平台,其定义的内容略有不同,本人是用模拟器调试的,所以我们看一下x86_64下的宏定义,将isa结构中的宏定义替换掉后,简化如下:

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

    Class cls;
    long bits;
#if defined(ISA_BITFIELD)
    struct {
      long nonpointer        : 1;
      long has_assoc         : 1;   
      long has_cxx_dtor      : 1;  
      long shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ 
      long magic             : 6;   
      long weakly_referenced : 1;   
      long deallocating      : 1;  
      long has_sidetable_rc  : 1;   
      long extra_rc          : 8
    };
#endif
};

简化后就好看多了,对了还有个cls成员变量,其类型是Class,这是什么,看下面:

typedef struct objc_class *Class;

所以cls是一个指针。

我们整体看一下isa的结构,不难看出,这是一个我们之前文章提到的联合体位域结构,联合体内的所有成员变量共享内存空间,其空间的大小取决于占用空间最大的成员变量,从结构中看,cls是指针,占8字节,bits是long类型,占8字节,所以整个isa占8字节,64位。

既然用到了联合体位域,那目的肯定是要节省内存空间,下面看看这64位都是存储哪些数据呢:

1.png

3. isa原理

上面了解了isa的结构,那么它究竟是干什么的呢?我们首先看一下对象的祖宗NSObject的底层结构:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

由这段代码我们可知,任何继承NSObject的对象,默认都会有一个成员变量isa,而且这个isa永远在成员列表的第一位,即使内存有优化。

我们在查看底层代码的时候也能看到:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

追踪底层代码可知,objc_object有isa变量,而objc_class继承objc_object,故而我们的类也是有isa成员变量的。关于类的探索,后续文章会更新。

知道isa的位置后,再看看isa的作用,isa的类型是Class,也就是isa指向的是一个类,那么这个类是什么呢?

之前的文章OC对象底层探索中提到对象的空间开辟完成之后,需要初始化对象的isa,如下:

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

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

由上面第二个方法中,在调用这个方法的时候nonpointer为true,所以会走到else分支,同时又不支持SUPPORT_INDEXED_ISA,所以代码最终走到#else分支,在这里有个很重要的赋值,那就是:

newisa.shiftcls = (uintptr_t)cls >> 3;

为什么重要,因为shiftcls记录的类的相关信息,而其值是由cls右移3位得到的。当isa初始化完成,isa的bits值里面就包含了类的信息。

那么如果获取到对象isa里面的类的信息呢?首先请看下面这个运行时API:

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

这个方法里面调用了obj->getIsa()

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

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}

这个方法中第一个判断**!isTaggedPointer()**,因为当前对象不是taggedPointer,所以执行 ISA() 方法。

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

这个方法中很显然会走到#else分支,我们得到的是: return (Class)(isa.bits & ISA_MASK); 而ISA_MASK的定义如下:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# else
#   error unknown architecture for packed isa
# endif

最终我们可以理解为:对象的isa里面关联了类的信息,并且该类是通过isa的bits与ISA_MASK做按位与运算得来的。

下面做个简单的验证:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GYMPerson *person = [GYMPerson alloc];
        object_getClass(person);
    }
    return 0;
}

我们在object_getClass(person);这行打个断点进行lldb调试,结果如下:

2.png

由上面可知,对象的isa关联对象的类。那么问题来了类的isa关联的是什么呢? 咱们简单了解一些,万物皆对象,所以,类在底层也是一个对象,类对象,为什么这么说呢?请看底层代码:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
}

这里只是摘抄了一部分代码,底层的类也是继承objc_object,那么类对象在内存中会创建多少呢?又是谁创建的呢? 类对象是由系统在编译期创建的,而且在内存中只创建一份,而类对象的isa则是关联了它的元类。

-(void)classNumberTest {
    Class class1 = [GYMPerson class];
    Class class2 = [GYMPerson alloc].class;
    Class class3 = [GYMPerson alloc].class;
    Class class4 = object_getClass([GYMPerson alloc]);
    NSLog(@"\nclass1 address = %p\nclass2 address = %p\nclass3 address = %p\nclass4 address = %p\n", class1,class2,class3,class4);
}

输出结果为:

class1 address = 0x10234c7d8
class2 address = 0x10234c7d8
class3 address = 0x10234c7d8
class4 address = 0x10234c7d8

由此可以证实类在内存中只有一份。 到此,我们可以简单总结一下isa的指向:对象的isa指向类,类的isa指向了元类。

4. isa走位

上面分析了对象的isa情况,又提到了类的isa情况,但是这只是一小部分,上面说类的isa指向了元类,那么元类也是类,元类的isa又指向哪里呢?在这里我们就不继续调试分析了,看看苹果官方给出的图,如下:

3.png

这个图中包含了2个走向,一个是superclass,一个是isa。 我们先来看看isa情况: 对象的isa指向类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向了根元类(即自己)。 其次是superclass的情况: 继承关系大家都比较了解,这里主要强调两点,一是NSObject的父类是nil,而是根元类的父类是NSObject。 苹果的这个图已经非常简单明了,这里就不再赘述了。

求喜欢iOS的小伙伴关注 !喜欢的话给一个赞吧!谢谢!谢谢!谢谢!

————————————————

点击获取更多资料

收录:原文链接