美文网首页
OC对象(三)-- isa结构分析

OC对象(三)-- isa结构分析

作者: 过气的程序员DZ | 来源:发表于2020-09-10 13:24 被阅读0次

    OC对象(一)-- alloc和init底层到底在干嘛
    OC对象(二)-- 内存对齐和calloc中的16字节对齐
    OC对象(三)-- isa结构分析

    开场白

    本文主要讲解isa结构和isa的赋值过程

    1、isa

    实例对象在内存中首地址就是isa,其实就是用来表示对象的类是谁。

    DZPerson *obj = [[DZPerson alloc] init];
    obj.name = @"DZ";
    NSLog(@"%@", obj.name);
    

    通过lldb打印obj的内存情况:

    通过调试,po一下obj首地址中的第一个值是DZPerson。说明isa中存储着实例对象对应的归宿类。

    2、isa结构


    通过objc源码查看isa是一个union位域的形式,源码如下:

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD) //ISA_BITFIELD存在
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    

    条件编译命令中的ISA_BITFIELD是存在的,所以后面的代码是会被编译进去的。接下来看看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)
    
    • 这里使用条件编译命令区分__arm64__代表iOS。__x86_64__代表MAC
    • ISA_MASK:掩码,用于直接获取isa中类信息的。也就是通过掩码可以直接得到shiftcls
    • nonpointer:表示是否对isa指针开启指针优化
      • 0:isa就是一个指针
      • 1:isa不只是一个简单的指针,里面还包含了类信息,引用计数等等信息。通常自定义的类实例对象都是这个类型。
    • has_assoc:是否有关联对象
    • has_cxx_dtor:是否有c++或Objc析构器
    • shiftcls:存储类指针的值。注意iOS和Mac中占用的位数不一样
    • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
    • weakly_referenced:对象有没有被若引用
    • deallocating:对象是否正在释放中
    • has_sidetable_rc:是否有引用计数散列表
    • extra_rc:提供给引用计数使用的。

    扩展 - union位域

    union位域可以节省内存空间的开辟,举个例子:
    定义一个DZPeront类,里面含有四个属性,分别代表走路的四个方向

    @interface DZPerson : NSObject
    @property (assign, nonatomic) BOOL front;
    @property (assign, nonatomic) BOOL back;
    @property (assign, nonatomic) BOOL left;
    @property (assign, nonatomic) BOOL right;
    @end
    

    这四个属性都是BOOL类型,一个BOOL类型占用1个字节。因此需要内存开辟4字节的空间。BOOL类型其实就是两种值YES or NO,对应的二进制是0000 0001 or 0000 0000,如图:

    如果把前面的几位利用上,就可以达到减少内存占用。使用union位域就可以实现这个目的。

    union {
        char bits;
        struct{
            char front :1;
            char back :1;
            char left :1;
            char right :1;
        };
    } _walkDirection;
    

    联合体(union)中还包含了一个结构体(struct),这个结构体中的成员后面跟着的冒号和数字,数字就是代表占用的位数(语法要求,这个要死记硬背),这个就是位域
    需要注意:

    • 案例中的数字“1”,代表占用1位。1位可以表达的两种情况:1和0。如果想表达“0-7”的值,那么就需要定义为“3”

    • 位域中成员定义的位是按从低到高的方式存储的

      nonuse nonuse nonuse nonuse right left back front
      0 0 0 0 1 1 1 1

    上面的DZPerson可以修改成:

    @interface DZPerson : NSObject{
        union {
            char bits;
            struct{
                char front :1;
                char back :1;
                char left :1;
                char right :1;
            };
        } _walkDirection;
    }
    
    - (void)setFront:(BOOL)isFront;
    - (BOOL)isFront;
    
    - (void)setBack:(BOOL)isBack;
    - (BOOL)isBack;
    
    @end
    
    @implementation DZPerson
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _walkDirection.bits = 0b00000000;
        }
        return self;
    }
    
    - (void)setFront:(BOOL)isFront {
        if (isFront) {
            _walkDirection.bits |= kPersonWalkDirectionFrontMask;
        } else {
            _walkDirection.bits |= ~kPersonWalkDirectionFrontMask;
        }
    }
    
    - (BOOL)isFront {
        return _walkDirection.front;
    }
    
    - (void)setBack:(BOOL)isBack {
        _walkDirection.back = isBack;
    }
    
    - (BOOL)isBack {
        return _walkDirection.back;
    }
    @end
    
    //调用
    DZPerson *obj = [[DZPerson alloc] init];
    [obj setFront:YES];
    [obj setBack:YES];
    NSLog(@"%@", obj.isFront ? @"YES": @"NO");
    

    查看obj内存情况,如图:


    此处内存中十六进制是0x3,因为调用代码设置的frontback,二进制表示:0b11

    3、isa的赋值过程

    实例alloc有重要的三步骤(不清楚的可以翻看之前的博客)

    1. 计算需要分类的空间大小:size = cls->instanceSize(extraBytes);
    2. calloc分配内存:obj = (id)calloc(1, size);
    3. 实例对象的初始化:obj->initInstanceIsa(cls, hasCxxDtor);

    此处主要聊聊第三部做了什么:

    static ALWAYS_INLINE id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                                  int construct_flags = OBJECT_CONSTRUCT_NONE,
                                  bool cxxConstruct = true,
                                  size_t *outAllocatedSize = nil)
    {
        ...//省略不关心代码
        size = cls->instanceSize(extraBytes);
        ...//省略不关心代码    
        obj = (id)calloc(1, size);
        ...//省略不关心代码
        
        //核心入口,加星标记🦍🦍🦍🦍🦍🦍🦍🦍 
        obj->initInstanceIsa(cls, hasCxxDtor);
        //🦍🦍🦍🦍🦍🦍🦍🦍 
        
        ...//省略不关心代码
        return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    
    ⏬⏬⏬
    
    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) {//传入参数是true,不会走这个分支
            ......
        } 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;
        }
    }
    

    核心研究的地方就是对isa中的位进行赋值,注意类的相关信息赋值在newisa.shiftcls位中。

    扩展 - 获取isa中的类信息

    runtime的api中提供了获取对象类方法object_getClass,接下来研究一下源码的实现:

    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    ⏬⏬⏬
    
    inline Class 
    objc_object::getIsa() 
    {
        if (fastpath(!isTaggedPointer())) return 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
    }
    
    • 最后调用的是isa.bits & ISA_MASK,这一步获取的就是isa中shiftcls的值(ISA_MASK上面有讲过,是个宏,在不同操作系统定义的值也不同)。
    • 得到的值进行Class类型强转,这也是为什么我们在看相关源码中,isa都是定义成Class类型原因。

    相关文章

      网友评论

          本文标题:OC对象(三)-- isa结构分析

          本文链接:https://www.haomeiwen.com/subject/bbcpektx.html