1OS底层-对象本质&联合体&isa关联

2020-09-11  本文已影响0人  lkm_0bdc

之前一直讲述对象本质,它的底层又做了什么。

在探索对象本质之前我们需要了解一下clang

Clang创建.cpp文件

首先我们需要打开终端,cd main文件目录下的路径
然后输入clang -rewrite-objc main.m -o main.cpp创建main.cpp文件。


通过搜索LGPerson定位到到struct 结构体,通过name我们可以知道对象编译的底层就是结构体。

mian.cpp文件中有多处都使用了NSObject_IVARS,其实它就是就是一个指针。

mian.cpp文件文件中的_I_LGPerson_name是一个get方法,_I_LGPerson_setName是set方法

set方法有一个objc_setProperty方法,所有set的方法都会调用,它类似一个工厂模式。

objc4-781编译源码中搜索objc_setProperty方法

跳转objc_setProperty源码中

跳转reallySetProperty源码中,可以看到objc_setProperty方法的具体实现,对新值的return,对旧值的release

每个属性的set方法,都做同样的事情(对新值的return,对旧值的release),因为每个属性对接下层 ,系统llvm会生成很多的临时变量,很难查找,所以所有的set方法,都会走objc_setProperty函数。

我们可以根据cmd区分属性

oc的对象就是结构体本质,继承于NSObject的isa ,同时我们分析到了非常重要且非常著名的objc的initinstanceIsa,在开始讲之前我们先扩展一个内容就是联合体位域

首先对象内存的开辟来自于属性,LGCar类有四个属性,假如四个属性都是int类型,那么4*4 = 16字节16 * 8位 = 128位这四个只是简单的标识,128位实在是太浪费了,所以为了节省空间,我们不需要属性,而是通过set和get方法。set方法通过1字节 = 8位存储,在0000 1111中对应front,back,left,right属性(结构体的顺序是从前往后),如果是位运算进行处理就是char+位域

@interface LGCar : NSObject
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
- (void)setFront:(BOOL)isFront;  // 存储 : 1字节 = 8位 0000 1111  char + 位域  bit 结构体
- (BOOL)isFront;

- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;

@end

结构体存在共存的关系,LGCar类的属性中,例如:向前就没有必要向后,同时为了更为精细灵活以及节省空间,我们就引入了联合体位域。

isa的类型isa_t

以下是isa指针类型的isa_t定义,从定义可以看出使用了联合体(union)

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

isa_t类型使用联合体(union),是因为内存优化的考虑,这里的内存优化是char+位域的实现,通常说一个isa指针占用8字节,即64位,已经足够存储很多信息,极大的节省内存,提高性能。

在isa中定义可以看出:

NONPOINTER_ISA

原理探索

通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone方法路径,查找到initInstanceIsa,并进入其原理实现

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

    initIsa(cls, true, hasCxxDtor);
}

跳转进入initIsa源码中

objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)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;
        // 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
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

验证isa指针位域

根据前文提及的0-64位域,可以在这里通过initIsa方法中证明有isa指针中有这些位域(目前是处于macOS,所以使用的是x86_64)

通过newsize的信息对比,发现isa指针中有一些变化,如下图所示


isa 和bits数据比对.jpg

其中magic的值是59,是由于isa指针地址转为二进制,从47位开始读取6位,再转为二进制

magic的说明

isa和类的关联

cls 与 isa 关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来,有以下几种验证方式:

方法一:通过initIsa方法

与bits赋值结果对比,有两处发生改变

由此可以观察isa初始化到isa的shiftcls的赋值过程即值的改变过程,如图

shiftcls赋值.png

为什么在shiftcls赋值时需要类型强转?

因为内存的存储不能存储字符串,机器码只能识别 0 、1这两种数字,所以需要将其转换为uintptr_t数据类型,这样shiftcls中存储的类信息才能被机器码理解, 其中uintptr_t是long

为什么需要右移3位?

主要是由于shiftcls处于isa指针地址的中间部分,前面还有3个位域,为了不影响前面的3个位域的数据,需要右移将其抹零。

方式二:通过isa & ISA_MSAK
继续执行,回到_class_createInstanceFromZone方法,此时cls 与 isa已经关联完成,执行po objc

方式三:通过 object_getClass

通过查看object_getClass的源码实现,同样可以验证isa与类关联的原理,有以下几步:
main中导入#import <objc/runtime.h>
通过runtime的api,即object_getClass函数获取类信息


方式四:通过位运算

上一篇下一篇

猜你喜欢

热点阅读