[iOS开发]runtime中基本结构体及OC对象底层探索


三个问题先放前面

  • 一个NSObject对象占用多少内存?
  • 对象的isa指针指向哪里?
  • OC的类信息存放在哪里?

在了解过后我们应该就会对这三个问题的答案有数了

类与对象

类和对象的本质

对象部分

OC的类和对象本质上都是结构体,在编译时都会以结构体的形式被编译到二进制中。
直接干源码

关于对象的源码 我们之间找到objc_object

struct objc_object {
private:
    isa_t isa;

public:
....省略方法部分

不看方法部分,我们可以发现其成员变量只有一个isa_t类型的isa

isa_t这个类型只有一个成员变量 存储了对象所属类的信息[Class cls]

ok对象的本质除了其本身含有的方法列表,它有一个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_class继承于objc_object,换句话说,类的本质也是个对象

那么类的isa存储的是什么呢?

再说之前先明白isa是结构体,其中会存储类的地址,其并不是指针
“isa指向”这句话严格来说是不正确的 但是方便理解

在此就要引入元类和根类(一般情况下就是NSObject)的概念了
在这里插入图片描述

[iOS开发]类,父类,元类的关系总结

今年3月有写过 直接拿来用了 很好理解

isa详解

来详细讲解一下isa指针 即isa_t结构体

isa_t类型我们之前ARC的objc_retain部分已经有所了解

不过这里我们和ARC关注的重点不同

每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。

我们还是看一下isa_t这个定义

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
         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;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
        uintptr_t extra_rc          : 19;  //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
    };
};

在这里插入图片描述

在这里插入图片描述
实际上,在64bit开始,isa需要进行一次位运算即 isa & ISA_MASK,才能计算出指向的类或元类对象的真实地址
在这里插入图片描述

isa_t的初始化

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;
}

assert()函数

其作用是如果它的条件返回错误,则终止程序执行。
assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。

初始化过程

newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE

这个里面说的很明白 news.bits初始化时候只设置了nonpointer,magic两个部分
可以看一下 在调用完后打印一下newisa
在这里插入图片描述

然后给时has_cxx_dtor的赋值,这一位表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。

下一步就是赋值shiftcls
在setClass里面实现 存储着Class\Meta-Class对象的内存地址信息

给extra_rc
的赋值1???

相比于之前的rootRetain部分的实现,逻辑说不过去,索性搞了个新版的源码,果然retainCount部分已经发生了改变 没有加一的实现了 而是在initIsa的时候进行初始化直接赋值一的操作 省略了retainCount部分的加一

class方法

首先Class这里需要注意几点

  1. class ObjectClass = [[nsobject class]class]; 返回的还是class对象,并不是meta-class对象。- (Class)class, +(Class)class返回的就是类对象
//instance实例对象
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];

        //class对象,类对象
        //objectClass1~5都是NSObject的类对象
        Class objectClass1 = [object1 class];
        Class objectClass2 = [object2 class];
        Class objectClass3 = [NSObject class];
        Class objectClass4 = object_getClass(object1);
        Class objectClass5 = object_getClass(object2);
        
        //元类对象(将类对象当作参数传入进去)
        Class objectMetaClass = object_getClass([NSObject class]);
        Class objectMetaClass2 = [[NSObject class] class];
        
        //判断是不是元类对象

        NSLog(@"instance - %p %p", object1, object2);
        NSLog(@"class - %p %p %p %p %p %d", objectClass1,objectClass2, objectClass3, objectClass4, objectClass5, class_isMetaClass(objectClass3 ));
        NSLog(@"mateClass - %p %p %d",objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));

输出情况:
instance - 0x100511920 0x10050e840
class - 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0
mateClass - 0x7fff91da20f0 0x7fff91da2118 1

无论多少次class方法得到的都还是类函数。
  1. 元类对象和class内存结构是一样的 但是用途不一样 主要有类方法的类信息 其他为空的
  2. 类对象在内存中有且仅有一个对象 主要包括 isa指针 super Class指针 类的属性信息 类的对象方法信息 类的协议信息 类的成员变量信息

看一下objc_class的源码部分

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;              // 方法缓存 formerly cache pointer and vtable
    class_data_bits_t bits;    // 用于获取具体的类信息 class_rw_t * plus custom rr/alloc flagsflags
    ...

bits里面存储了类的方法列表等等的信息

这里的bits是class_data_bits_t类型的,上面objc_object的isa_t类型数据中也有一个uintptr_t类型的bits,但是这是两种结构。

class_rw_t和class_ro_t

bits里面有class_rw_t,class_ro_t两个方法

ro:
在这里插入图片描述
rw:
在这里插入图片描述

二者相比 首先最直观的可以看到只读文件存储的类的属性、方法、协议前面都有Base 并且多了一个ivars(成员变量)
而读写并没有这个成员变量

所以为后面做个铺垫 通过runtime动态修改类的方法时,其实是修改在class_rw_t区域中存储的方法列表

在这里插入图片描述
在这里插入图片描述

流程总结

methodizeClass方法、realizeClass方法

  • 编写代码运行后,开始类的方法,成员变量 属性 协议等信息都存放在 const class_ro_t中
  • 运行过程中,会将信息整合,动态创建 class_rw_t
  • 然后会将 class_ro_t中的内容(类的原始信息:方法 属性 成员变量 协议信息) 和 分类的方法 属性 协议 成员变量的信息 存储到 class_rw_t中.并通过数组进行排序,分类方法放在数组的前端,原类信息放在数组的后端.
  • 初始运行时 objc-class中的 bits是指向 class_ro_t的,bits中的data取值是从class_ro_t中获得,而后创建 class_rw_t,class_rw_t中的 class_ro_t从初始的 class_ro_t中取值(所以这两个不是一个对象),class_rw_t初始化完成后,修改 objc_class中的bits指针指向class_rw_t

cache_t

struct cache_t {
	    struct bucket_t *buckets() const;
	    mask_t mask() const;
	    mask_t occupied() const;
}

这里引入了bucket_t(散列表)
cache_t 哈希表结构,哈希表内部存储的bucket_t
bucket_t中存储的是SEL和IMP的键值对

在这里插入图片描述
在这里插入图片描述

property

我们都只知道property会帮属性自动生成set\get方法和一个成员变量

struct property_t {
    const char *name;
    const char *attributes;
};

我们只储存了其name和属性关键字
在这里插入图片描述

总结

第一个问题看这篇就足够了
内存对齐

第二个问题对象的isa指针指向哪里
答:每一个实例变量的isa都指向自己所属的类,每一个类的isa都指向自己所属的元类,同时,父类的元类也是子类的元类的父类,父类的元类也是根元类的子类。而父类的元类和子类的元类都属于根元类。根元类的isa指向自己,同时根元类的父类也是自己的下属类(NSObject元类的父类是NSObject类)。

OC的类信息存放在哪里?
答:对象方法、属性、成员变量、协议信息,存放在class对象中
类方法存放在meta-class对象中(元类对象和class内存结构是一样的 但是用途不一样 主要有类方法的类信息 其他为空的)
成员变量的具体值存放在instance对象中

遇到的问题

不过还有问题

  1. extra_rc这个存储信息为什么是引用计数减一的结果
  2. extra_rc的值的改变一会儿加一,一会儿减一 把人搞晕了
  3. 在自己实际测试时,为什么就算我main函数中什么也不写,在initIsa中打断点也在一直调用?很多次之后才会结束,编译器帮我们做了什么工作?
  4. 818rootRetainCount函数的实现相比于750的进行了什么优化?
  5. alloc的创建过程到底是怎么样的?

为什么要考虑这些问题呢?我只需要理解其过程就好了 何必在乎内部的运行呢请添加图片描述

在这里插入图片描述

导入#import "objc/runtime.h"之后的object_getClass就是得到其isa指针
在这里插入图片描述

superClass的得到的是父类指针。

这个图太重要了
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值