Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

对象的本质(3) #7

Open
lincf0912 opened this issue Jun 10, 2021 · 0 comments
Open

对象的本质(3) #7

lincf0912 opened this issue Jun 10, 2021 · 0 comments

Comments

@lincf0912
Copy link
Owner

了解 isa 是什么

将Objective-C代码转换为C\C++代码

clang -rewrite-objc OC源文件 -o 输出的CPP文件

NSObject的OC与C++定义

  • 在OC中的定义

    @interface Student : NSObject
    {
        @public
        int _no;
        int _age;
    }
    @end
  • 转成C++之后的定义

    struct Student_IMPL {
    	struct NSObject_IMPL NSObject_IVARS;
    	int _no;
    	int _age;
    };
    
    struct NSObject_IMPL {
    	Class isa;
    };
    
    
    //其实就是
    struct Student_IMPL {
    	Class isa; //8字节
    	int _no; //4字节
    	int _age; //4字节
    };

对于结构体来说,和数组一样。其第一个成员的地址,即为结构体对象的地址。 所以一个OC对象的地址,实际上就是其isa指针的地址。

而这个isa是指向objc_class结构体的指针

// 指针
typedef struct objc_class *Class;

Class对象其实是一个指向objc_class结构体的指针。因此我们可以说类对象或元类对象在内存中其实就是objc_class结构体。而一个指针在64位系统中所占的内存为8字节。

所以一个OC对象所占的内存至少为8字节

通过objc源代码中去查找objc_class结构体的内容

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // 方法缓存
    class_data_bits_t bits;    // 具体的类信息

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ...
}

// bits.data()的实现部分:
// class_rw_t是通过bits调用data方法得来的,data函数内部仅仅对bits进行&FAST_DATA_MASK操作
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

我们发现这个结构体继承 objc_object 并且结构体内有一些函数,因为这是c++结构体,在c上做了扩展,因此结构体中可以包含函数。我们来到objc_object内,截取部分代码

image-20210610225428367

我们发现objc_object中有一个isa指针,那么objc_class继承objc_object,也就同样拥有一个isa指针,这个isa_t后面再详细描述。

class_data_bits_t(类信息列表)内部,还保存着class_rw_t(可读写信息列表),这些信息是可以动态修改的

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags; // 标记
    uint32_t version; // 版本信息

    const class_ro_t *ro;

    method_array_t methods; // 方法列表
    property_array_t properties; // 属性列表
    protocol_array_t protocols; // 协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
    ...
}

class_rw_t(可读写信息列表)内部,还保存着class_ro_t(不可变信息列表),保存着类加载进内存时就需要确定的信息

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; //实例对象所占内存大小
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name; //类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars; //成员变量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

image-20210610225123239

image-20210610233257969

不只是 实例对象 会包含一个 isa 结构体,所有的 类对象 都包含一个 isa。在 ObjCClass 的定义也是一个名为 objc_class 的结构体,到这里,我们就可以得出一个结论:Objective-C 中类也是一个对象。所以一个OC对象的本质实际上是一个包含了所有父类成员变量+自身成员变量的结构体

最后用一张图来总结

image-20210610233546728

特殊结构体 isa_t

以 x86_64 架构下为例,可以在 ObjC 源码中可以看到这样的定义:

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

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 是一个 union 的结构体,也就是说其中的 clsbits 共用同一块内存地址空间,而 isa 总共会占据 64 位的内存空间。

结构体(struct)和联合体(union)的区别

  1. 结构体struct

    各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。
    
  2. 联合体union

    各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。不允许对联合体变量名U2直接赋值或其他操作。
    

以下是 isa 64位 bits 所代表的意思:

  • nonpointer: 表示是否对 isa 指针开启指针优化

    • 0: 纯isa指针,
    • 1: 不止是类对象地址,isa 中包含了类信息、对象的引用计数等。
  • has_assoc: 关联对象标志位,

    • 0: 没有,
    • 1: 存在。
  • has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有, 则可以更快的释放对象。

  • shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33位用来存储类指针。

  • magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间。

  • weakly_referenced: 标志对象是否被指向或者曾经指向一个 ARC 的弱变量,
    没有弱引用的对象可以更快释放。

  • deallocating: 标志对象是否正在释放内存

  • has_sidetable_rc: 当对象引用计数大于 10 时,则需要借用该变量存储进位。

  • extra_rc: 当表示该对象的引用计数值,实际上是引用计数值 减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到上面的 has_sidetable_rc

isa 的初始化

通过 isa 初始化的方法 initIsa 来初步了解这 64 位的 bits 的作用:

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{     
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        isa_t newisa(0);
        
        newisa.bits = ISA_MAGIC_VALUE; // 0x001d800000000001ULL
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
        
        isa = newisa;
    }
}

nonpointer 和 magic

  1. 当我们对一个 ObjC 对象分配内存时,其方法调用栈中包含了上述的两个方法,这里关注的重点是 initIsa 方法,由于在 initInstanceIsa 方法中传入了 nonpointer = true。对整个 isa 的值 bits 进行设置,传入 ISA_MAGIC_VALUE0x001d800000000001ULL

  2. 在使用 ISA_MAGIC_VALUE 设置 isa_t 结构体之后,实际上只是设置了 nonpointer 以及 magic 这两部分的值

    • 其中 nonpointer 表示 isa_t 的类型

      • 0: 表示 raw isa,也就是没有结构体的部分,访问对象的 isa 会直接返回一个指向 cls 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型。
      • 1: 表示当前 isa 不是指针,但是其中也有 cls 的信息,只是其中关于类的指针都是保存在 shiftcls 中。

has_cxx_dtor

在设置 nonpointermagic 值之后,会设置 isahas_cxx_dtor,这一位表示当前对象有 C++ 或者 ObjC的析构器(destructor),如果没有析构器就会快速释放内存。

newisa.has_cxx_dtor = hasCxxDtor;

shiftcls

在为 nonpointermagichas_cxx_dtor 设置之后,我们就要将当前对象对应的类指针存入 isa 结构体中了。

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

将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除进而减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。

(lldb) po obj
<Person: 0x101121230>
(lldb) x/4gx 0x101121230
0x101121230: 0x001d8001000020e9 0x0000000000000000
0x101121240: 0x726573554b575b2d 0x7320747069726353
(lldb) po 0x001d8001000020e9 & 0x00007ffffffffff8ULL
Person
(lldb) 

我们将 isa & ISA_MASK 得到了我们的 Class。这也就验证了我们之前对于初始化 isa 时对 initIsa 方法的分析是正确的。它设置了 nonpointermagic 以及 shiftcls

getIsa() 方法

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

inline Class 
objc_object::getIsa() 
{
    return ISA();
}

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant