iOS类的底层探究分析

前言

一改c语言的面向过程开发模式,OC语言对c和c++语言进行了高度封装,成为了一种面向对象开发的语言,对于有一定开发经验的开发这来讲,一个项目势必是由多个不同的类组成;每一个类中都包涵自己的属性,成员变量,方法函数等,然而,这些在底层是如何表现的呢?我们今天就一起探究分析一下:

一:isa的走向

我们都知道当我们用一个类创建一个对象以后,我们在控制台通过p/x 命令可以看到该对象的内存分配的结构,并且可以找到每个对象的isa地址,我们可以通过下图来简单了解一下:

image.gif

首先,我们声明了一个LGPerson类,然后用它创建了一个对象p,通过p/x p命令我们可以获取到p对象的首地址为:0x000000010072a840,然后我们通过x/4gx p可以获取到p的内存分布结构,从显示出来的内容我们可以得到p的isa内存地址为:0x011d800100008365,此时我们可以通过与掩码的与运算来验证一下;

image.gif

通过验证我们可以得处,0x011d800100008365就是p的isa地址;测试我们通过isa和掩码做与运算可以得到当前的类的地址0x0000000100008360,通过po可以打印出LGPerson,以此可以验证isa的准确性;
做完上边的操作以后,我们可以或许会想到,如果我x/4gx 0x0000000100008360这个地址会得到什么呢?接下来我们一块儿来验证一下:

image.gif

从图片中我们可以看到,当我们执行了x/4gx 0x0000000100008360 这条命令以后我们有得到了一组新的数据,从这些数据中我们可以得到一个新的isa地址0x0000000100008338,此时我们使用这个isa和掩码做与运算,并且通过po 命令可以观察到同样输出一个LGPerson,到此处是否已经蒙圈了?为什么会这样呢?难道是类可以和对象可以在内存中无限开辟吗?我们接下来通过代码验证一下:

image.gif

我们通过这样一段代码,创建了多个对象,并且来打印一下他们的内存地址,通过观察我们可以知道,打印出来的地址是完全一样的都是0x0000000100008360 ,这和我们前边第一次得到的类的内存地址是一样的,因此我们可以知道0x0000000100008360 才是我们当前类的的内存地址;但是我们上边得到的那个新的isa 0x0000000100008338又是什么呢?
我们可借助MachOView工具来分析。

image.gif

如上图所示,打开MachOView以后,我们把项目当中的Products目录下的生成的可执行文件拖拽进去可打开,我们打开Section64(__DATA_objc_classrefs),这时候我们可以看到0x0000000100008360,这不就是我们之前得到的类的内存地址嘛;
我们接着往下看:

image.gif

当我们打开Symbol Table下边的*Symbols并在右上角的输入框中输入class关键字,我们可以找到图中的内容,这时候我们可以看到,当系统将我们的代码编译以后会生成_OBJC_CLASS__LGPreson和_OBJC_METACLASS__LGPerson,然而_OBJC_METACLASS是什么呢,我们在工程中并未创建这个类型的类呀,这时候我们可以理解为,是系统在编译过成中帮我们自动生成的metaclass,我们可以称之为元类;并且在执行过程中类的isa指向元类的isa,所以我们上边得到的0x0000000100008338就是我们创建的LGPerson的类的元类的isa;
以上我们探究的过成我们可以使用下边的图片来完美的展示出来:

image.gif

由上图我们可以看出来,一个对象的isa指向类的isa,类的isa指向元类的isa,而元类的isa直接指向根元类的isa,跟元类的isa指向他自己

二:类的继承走向

上边我们一块儿探究类一下iOS中isa的走位,那么iOS中类的继承走向是否和isa的一样呢,我们接着往下看;首先我们先看一段代码:

image.gif

我们通过上边的代码可以得到NSObject的类,元类,根元类,根根元类,经过打印我们可以看出来NSObject的元类和根元类,和根根元类是相同的,此时我们在看下边的代码:

image.gif

我们有一个类是LGPerson,我们通过object_getClasss方法可以获取到LGPreson的元类,并且通过class_getSuperclass可以获取到元类的父类,经过打印我可以得到LGPerson元类的父类为NSObject,那如果现在有一个LGTeacher继承于LGPerson,我们获取LGTeacher的元类会是什么样子的呢?我们同样通过object_getClass和class_getSuperclass来获取,经过打印可以看出来,LGTeacher的元类继承于LGPerson;此时我们是不是会想,每一个子类都有自己的父类,那么是不是每一个父类都会有自己的父类呢?因此我们通过相同的方式对NSObjec进行了打印,可以看到NSObject继承null也就是说他没有自己的父类,但是如果是跟元类呢?经过测试我们可以得到,跟元类的父类为NSObjecj,这一切文字体现于图上是什么样的呢?请各位看官往下看:

image.gif
注:虚线为isa走位,实线为类的继承走位

总结:由上图我们可以得到,一个对象的isa指向类isa,类的isa指向元类的isa,元类的isa指向跟元类,根源类的isa指向他自己;
也能看出来,每一个对象都是由类创建,而类继承于自己的父类,父类最终会继承于根类,并且元类也会继承自己的父级元类,父级元类会继承于根源类,根元类继承于根类,而根类继承nil;

三:类的属性探究

在上边我们的知道了类和元类的继承关系,那么类中的属性是如何存储的呢?我接下来一块儿探究一下。
首先,我们知道类的本质是一个结构体,结构体所占内存大小是由结构体中的属性决定的,并且结构体中的属性地址都是连续的。我们来看一下类在底层的源码:

image.gif

根据图上源码结构分析的出,如果想要直接得到class_data_bits_t这个属性,需要类的内存首地址偏移32位;

image.gif

也就是上图所示,获取到LGPerson的首地址以后可以直接p/x 首地址加上0x20可以得到class_data_bits_t这个属性的地址,当我们拿到这个地址以后我们可以通过p $3->data()来拿到class_rw_t,那class_rw_t里边有什么内容呢?我们接着往下看

image.gif

使用p *$5 命令可以得到如上结果;
此时我们在看一下class_data_bits_t的源码结构,从结构中我们可以看到里边有对我们常见的methods,properties,protocols的处理;

struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif

explicit_atomic<uintptr_t> ro_or_rw_ext;

Class firstSubclass;
Class nextSiblingClass;
复制代码

private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR(“class_ro_t”), PTRAUTH_STR(“class_rw_ext_t”)>;

const ro_or_rw_ext_t get_ro_or_rwe() const {
    return ro_or_rw_ext_t{ro_or_rw_ext};
}

void set_ro_or_rwe(const class_ro_t *ro) {
    ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}

void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
    // the release barrier is so that the class_rw_ext_t::ro initialization
    // is visible to lockless readers
    rwe->ro = ro;
    ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}

class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
复制代码

public:
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}

void clearFlags(uint32_t clear) 
{
    __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}

// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear) 
{
    ASSERT((set & clear) == 0);

    uint32_t oldf, newf;
    do {
        oldf = flags;
        newf = (oldf | set) & ~clear;
    } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}

class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}

class_rw_ext_t *extAllocIfNeeded() {
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}

class_rw_ext_t *deepCopy(const class_ro_t *ro) {
    return extAlloc(ro, true);
}

const class_ro_t *ro() const {
    auto v = get_ro_or_rwe();
    if (slowpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
    }
    return v.get<const class_ro_t *>(&ro_or_rw_ext);
}

void set_ro(const class_ro_t *ro) {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
    } else {
        set_ro_or_rwe(ro);
    }
}

const method_array_t methods() const {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
    } else {
        return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
    }
}

const property_array_t properties() const {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
    } else {
        return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
    }
}

const protocol_array_t protocols() const {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
    } else {
        return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
    }
}
复制代码

};

此时我们通过p $5.properties可以得到如下结果

image.gif

这是我们继续往下走,通过下图的所示的方法我们最终可以得到在类中我们声明的属性

image.gif

那么,在类中我们声明的方法如何获取呢?请看下图:

image.gif

我们使用同样的操作,可以获取到方法列表,不过方法列表和属性是有一点区别的,通过源码分析我们可以得到,方法的name等属性是在big中存放的,所以我们可以通过p $18.get(0).big(),可以获取到方法的相关属性;

总结

通过以上的探索我们明白了
1.在类中isa的走向,对象的isa指向类的isa,类的isa指向元类的isa,元类的isa指向根源类,根源类的isa指向他自己;
2.类继承于父类,父类继承于自己的父类,父类的父类最终会继承根类NSObject,NSObject继承nil;
3.元类继承于父元类,父元类继承于自己的父元类,父元类的父元类最终会继承根类NSObject,NSObject继承nil;
4.我们可以通过class_data_bits_t结构并结合lldb可以查看出类的属性和方法;
类的属性方法探索并未完成,下篇文章继续探索

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享