类的原理分析(上)

objc1.jpeg

开局一张图,请看下面大家都熟悉的苹果官方给出的类的ISA走向类继承链的图解(我在原图上加了几个对象、类、元类的名称分别用不同颜色标注):

isa分析拓展到类和元类

上图:

isa流程图的副本.png

我们根据上图可以看到有两条线,一条虚线isa的走向路线,一条实线类继承链的走向。我们接下来就来验证下这张图片的两条线的走向结果。

ps:补充说明:下面的验证过程会用到前面文章讲到的掩码 ISA_MASK,在我当前的开发环境下从objc(objc4-818.2)源码拿到的ISA_MASK __x86_64__架构下:0x00007ffffffffff8ULL__arm64__ 架构下:0x0000000ffffffff8ULL ,使用的时候去掉后面的ULL,ULL只是表示这个是无符号长整型(unsigned long long)这里不再做过多赘述,不懂得看前面的文章 初探对象原理(三)

1,isa走向路线(虚线)

选取Person 对象这条线进行分析,其实选ioser 对象也是一样的。

(1)根据图文走向分析第一点:对象(person)isa指向类(ZYPerson),我们利用lldb来打印(打断点在NSLog处)验证:

直接上代码:

#import 
#import "ZYIoser.h"
#import "ZYPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        //掩码(x86_64):0x00007ffffffffff8
        
        ZYPerson *person = [ZYPerson alloc];
        NSLog(@"person====%@====%p",person,person);
    }
    return 0;
}
(lldb) p/x person
(ZYPerson *) $0 = 0x0000000100505640
(lldb) x/4gx 0x0000000100505640
0x100505640: 0x011d800100008345 0x0000000000000000
0x100505650: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008345 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008340
(lldb) po 0x0000000100008340
ZYPerson

(lldb) 

加上图解:

1.png

代码解释:
利用p/x perspn 得出指针地址0x0000000100505640 做格式化操作 x/4gx 取出personisa 指针地址0x011d800100008345,利用这个isa 0x011d800100008345ISA_MASK (0x00007ffffffffff8)与& 操作得出当前 person类 (class)的地址0x0000000100008340,在po打印该地址验证得出ZYPerson

结论:对象(person)isa指向他的类(ZYPerson)

(2)根据图文走向分析第二点:类(ZYPerson)isa指向元类(ZYPerson_meta),我们利用lldb来打印验证:

直接上代码:

(lldb) p/x person
(ZYPerson *) $0 = 0x0000000108104d10
(lldb) x/4gx 0x0000000108104d10
0x108104d10: 0x011d800100008345 0x0000000000000000
0x108104d20: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008345 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008340
(lldb) po 0x0000000100008340
ZYPerson

(lldb) x/4gx 0x0000000100008340
0x100008340: 0x0000000100008318 0x00007fff88962008
0x100008350: 0x00007fff201e9af0 0x0000802c00000000
(lldb) p/x 0x0000000100008318 & 0x00007ffffffffff8
(long) $3 = 0x0000000100008318
(lldb) po 0x0000000100008318
ZYPerson

(lldb) 

加上图解:

2.png

代码解释:

利用x/4gxZYPerson 指针地址0x0000000100008340 做格式化操作 取出ZYPersonisa 指针地址0x0000000100008318,利用这个isa指针地址和ISA_MASK (0x00007ffffffffff8)与& 操作得出一个地址0x0000000100008318,po打印该地址得出同样的ZYPerson。可以猜测这个和类地址0x0000000100008340不一样的地址0x0000000100008318就是我们常说的元类的地址。

我们还可以从另一方面查看这个特殊的地址到底是个啥,我们利用烂苹果(MachOView)来查看下Mach-O文件,我们把项目的Mach-O文件拉到MachOView打开,找到下面的符号列表,往下翻我们可以看到关于ZYPerson 类的部分 有一个奇怪的名字OBJC_METAcALSS_$ZYPeson从字面的意思我们也可以猜测这就是我们常说的元类并且他不是我们创建的而且跟ZYPerson绑定,证明是系统自己帮我们创建的。

3.png

结论:类(ZYPerson)isa指向他的元类(ZYPerson_meta)

(3)根据图文走向分析第三点:元类(ZYPerson_meta)isa指向根元类(NSObject_meta),我们利用lldb来打印验证:

直接上代码:

(lldb) x/4gx 0x0000000100008318
0x100008318: 0x00007fff88961fe0 0x00007fff88961fe0
0x100008328: 0x0000000100704e00 0x0002e03500000003
(lldb) p/x 0x00007fff88961fe0 & 0x00007ffffffffff8
(long) $5 = 0x00007fff88961fe0
(lldb) po 0x00007fff88961fe0
NSObject

(lldb) 

加上图解:

4.png

代码解释:

利用x/4gxZYPerson_meta 指针地址0x0000000100008318 做格式化操作 取出ZYPerson_metaisa 指针地址0x00007fff88961fe0,利用这个isa指针地址和ISA_MASK (0x00007ffffffffff8)与& 操作得出一个地址0x00007fff88961fe0,po打印该地址得出同样的NSObject。证明元类 ZYPerson_metaisa 指向 根元类 NSObject_meta

结论:元类(ZYPerson_meta)isa指向他的根元类(NSObject_meta)

(4)根据图文走向分析第四点:根元类(NSObject_meta)isa指向自己(NSObject_meta),我们利用lldb来打印验证:

直接上代码:

(lldb) p/x NSObject.class
(Class) $8 = 0x00007fff88962008 NSObject
(lldb) x/4gx 0x00007fff88962008 & 0x00007ffffffffff8
error: memory read takes a start address expression with an optional end address expression.
Expressions should be quoted if they contain spaces or other special characters.
(lldb) x/4gx 0x00007fff88962008
0x7fff88962008: 0x00007fff88961fe0 0x0000000000000000
0x7fff88962018: 0x000000010855c3b0 0x0001801000000003
(lldb) p/x 0x00007fff88961fe0 & 0x00007ffffffffff8
(long) $9 = 0x00007fff88961fe0
(lldb) po 0x00007fff88961fe0
NSObject

(lldb) 

加上图解:

5.png

代码解释:
利用p/x NSObject.classNSObject_meta 打印得到地址0x00007fff88962008 ,做格式化操作取出NSObject_metaisa 指针地址0x00007fff88961fe0,利用这个isa指针地址和ISA_MASK (0x00007ffffffffff8)与& 操作得出一个地址0x00007fff88961fe0,po打印该地址得出同样的NSObject。证明根元类 NSObject_metaisa 指向 根元类 NSObject_meta自己。

结论:根元类(NSObject_meta)isa指向自己(NSObject_meta)

2,类继承链的走向(实线)

根据上面isa走向的分析我们下面可以用同样的方法来验证类继承链的走向。这里我们可以更简单的利用OC 本身自带的API来打印 出对应的信息。如下:

#pragma mark - NSObject 元类链
void ZYTestNSObject(void){
    /*
     * NSObject 继承链 分析 打印
     **/
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\nNSObject 继承链 分析 打印 :\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类\n",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    
    /*
     * ZYPerson 继承链 分析 打印
     **/
    // ZYPerson元类
    //获取ZYPerson 的元类pMetaClass
    Class pMetaClass = object_getClass(ZYPerson.class);
    //根据获取ZYPerson 的元类pMetaClass获取其 父类 psuperClass
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"\nZYPerson 继承链 分析 打印 :\n%@ - %p\n",psuperClass,psuperClass);
    
    /*
     * ZYIoser 继承链 分析 打印
     **/
    
    // ZYIoser -> ZYPerson -> NSObject
    // 元类也有一条继承链
    //获取ZYIoser 的元类iMetaClass
    Class iMetaClass = object_getClass(ZYIoser.class);
    //根据获取ZYIoser 的元类iMetaClass获取其 父类 isuperClass
    Class isuperClass = class_getSuperclass(iMetaClass);
    NSLog(@"\nZYIoser 继承链 分析 打印 :\n%@ - %p\n",isuperClass,isuperClass);
    
    
    /*
     * NSObject 继承链 验证 打印
     **/
    
    // NSObject 根类特殊情况 获取 NSObject 的父类
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"\n获取 NSObject 的父类 打印 :\n%@ - %p\n",nsuperClass,nsuperClass);
    // 根元类 -> NSObject 根据上面获取到NSObject 的元类 去获取其根源类
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"\n获取NSObject元类的根源类 打印 :\n%@ - %p\n",rnsuperClass,rnsuperClass);

}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //掩码 0x00007ffffffffff8
//        ZYPerson *person = [ZYPerson alloc];
        
        // 继承链
        ZYTestNSObject();
    }
    return 0;
}
2021-06-22 10:08:32.579807+0800 ZYProjectFour001[90252:3084099] 
NSObject 继承链 分析 打印 :
0x105b3e900 实例对象
0x7fff808db388 类
0x7fff808db360 元类
0x7fff808db360 根元类
0x7fff808db360 根根元类

2021-06-22 10:08:32.580551+0800 ZYProjectFour001[90252:3084099] 
ZYPerson 继承链 分析 打印 :
NSObject - 0x7fff808db360

2021-06-22 10:08:32.580677+0800 ZYProjectFour001[90252:3084099] 
ZYIoser 继承链 分析 打印 :
ZYPerson - 0x100008360

2021-06-22 10:08:32.580735+0800 ZYProjectFour001[90252:3084099] 
获取 NSObject 的父类 打印 :
(null) - 0x0

2021-06-22 10:08:32.580818+0800 ZYProjectFour001[90252:3084099] 
获取NSObject元类的根源类 打印 :
NSObject - 0x7fff808db388

Program ended with exit code: 0

从上面我们可以看到:
1,NSObject类元类根元类根根元类都是同一个地址0x7fff808db360,所以我们可以验证NSObject元类NSObject_meta根元类自己 NSObject_meta.
2,从ZYPerson的继承链打印可以验证ZYPerson_meta根源类NSObject_meta
3,根据ZYIoser继承链分析打印可以验证,继承与ZYPerson类ZYIoser类元类ZYIoser_meta根元类ZYPerson_meta.
4,根据NSObject继承链验证打印可以验证:
(1)NSObject类的父类为null
(2)并且看到获取到的NSObject元类(NSObject_meta)根源类还是NSObject类自己因为打印的地址为:0x7fff808db388和 第一次打印的NSObject类的地址0x7fff808db388一样。

类的结构初探:

根据上面对类的继承、isa的走向分析,我们现在进一步探究,类它里面到底有什么:我们先直接利用lldb来打印一下

1.png

根据上面的打印进一步探究分析类的结构,那我们就回到我们的objc源码,我们通过前面对对象的原理分析可以知道在底层对于类Class也有对应的objc_class,那么我们就在全局搜索里去搜 objc_class的实现,我们可以发现有两个,但是仔细查看可以发现其中一个是在object2被废弃了。所以我们直接看另一个,代码如下:(代码太长我省略了部分方法静态变量不占用类内存的代码,我们要分析的是类的内存里面到底有什么)

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 flags

    /*
    *此处省略了部分代码
    */

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

  /*
    *此处省略了大量不占用类内存的方法以及其他代码
    */
};

经过简单分析我们得出的代码可以发现 占用内存的只有四个(必备的isasuperclasscachebits)。里面的isasuperclass我们都熟悉,但是cachebits我们不知道是什么,但是我们在clang的时候我们会发现methodslistivarlistpropety、等东西都存在bit里并且我们在代码的时候发现在注释和下方代码里有很多东西都是从bit里获取的比如class_rw_t,我们看看class_rw_t

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

    Class firstSubclass;
    Class nextSiblingClass;

   /*
    * ***********************删除了部分代码***********************
   */
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(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is())) {
            return v.get(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get(&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())) {
            return v.get(&ro_or_rw_ext)->ro;
        }
        return v.get(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            v.get(&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()) {
            return v.get(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get(&ro_or_rw_ext)->baseMethods()};
        }
    }

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

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get(&ro_or_rw_ext)->baseProtocols};
        }
    }
};

在上面代码中我们看到有许多method_array_tproperty_array_tprotocol_array_t。这个是否就是所谓的方法属性协议呢?
那我们就先探究下这里的bits

根据底层指针与取值的关系,我们知道在内存中平移指针可以取到不同内存区的值,所以我们是否可以利用这个方法来获取class_data_bits_t bits 保存的内容?

我们分析下:

在结构体objc_class中我们知道了 isa8字节、Class superclass是个结构体指针所以是 8 字节,但是cache多大我们不知道,所以我们先看看cache

cache_t

struct cache_t {
private:
    explicit_atomic _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic _originalPreoptCache;
    };
   /*
    *此处省略了大量不占用类内存的方法以及其他代码
    */
};

从上面代码可以看到内部占内存的就是一个联合体union,并且根据互斥原则 我们只需要看explicit_atomic _originalPreoptCache;的大小或者算处另外三个的大小之和就好了,我们看到mask_tuint32_t类型所以是 4字节,其他两个 uint16_t 都是 2字节
所以联合体总共8 字节,加上外部的explicit_atomic _bucketsAndMaybeMask ,这个主要看uintptr_t 的大小,我们知道这个是个指针所以 也是8字节,所以总共是8+4+2+2 = 16字节。

#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t SEL;

所以cache_t16字节

所以在结构体objc_class中我们可以从首地址往下平移就可以得到class_data_bits_t,现在我们知道了 isa8字节、Class superclass是个结构体指针所以是 8 字节,cache_t16字节,所以我们可以利用地址平移上面的总和isa 8 + superclass 8 + cache_t 16 = 32 字节 来获取剩下的class_data_bits_t bits 保存的内容。

ps:这里开始以下的代码都必须在我们下载下来的objc源码里来调试(因为自己创建的工程不包含class_data_bits_t这些东西所以你取的时候会报 未声明的class_data_bits_t

直接上代码:

#import 
#import "ZYPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 类的内存结构
        // 8 + 8 + 16 = 32
        ZYPerson *person = [[ZYPerson alloc] init];
    
        // class_data_bits_t
    }
    return 0;
}
(lldb) x/4gx ZYPerson.class
0x1000086e0: 0x00000001000086b8 0x000000010036a140
0x1000086f0: 0x0000000101409bd0 0x0001801000000003
(lldb) p/x 0x1000086e0+0x20
(long) $1 = 0x0000000100008700
(lldb) p (class_data_bits_t *)0x0000000100008700
(class_data_bits_t *) $2 = 0x0000000100008700
(lldb)  

代码解释图:

2.png

我们之前看objc源码的时候看到bits 取值首先是从一个data()里取如下:

struct objc_class : objc_object {
   //省略前面的代码
    class_rw_t *data() const {
        return bits.data();
    }
   //省略后面的代码
};

所以我们利用前面得到的强转对象$6来取data()

(lldb) x/4gx ZYPerson.class
0x1000086e0: 0x00000001000086b8 0x000000010036a140
0x1000086f0: 0x000000010070d450 0x0001803000000003
(lldb) p/x 0x1000086e0+0x20
(long) $1 = 0x0000000100008700
(lldb) p (class_data_bits_t *)0x0000000100008700
(class_data_bits_t *) $2 = 0x0000000100008700
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010070c570
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = {
      Value = 4295001072
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb)  

图文代码解释:

3.png

通过上面的代码我们发现到现在还没看到我们在ZYPerson类里设置的属性、方法等如图

4.png

这个时候我们回到class_rw_t结构体源码中找方法我们可以看到以下代码:

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

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

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get(&ro_or_rw_ext)->baseProtocols};
        }
    }

所以我们可以知道取值方法原来在这里,我们可以看到都是通过properties来取值下一层的。接下来我们根据这些方法对应去取值查看是否能找到我们在ZYPerson类里设置的属性和方法等。

属性:

我们根据代码property_array_t跟踪进去可以看到:

class property_array_t : 
    public list_array_tt
{
    typedef list_array_tt Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

接着跟踪list_array_tt

class list_array_tt  {
    struct array_t {
        uint32_t count;
        Ptr lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };

 protected:
    class iterator {
        const Ptr *lists;
        const Ptr *listsEnd;
        typename List::iterator m, mEnd;

     public:
        iterator(const Ptr *begin, const Ptr *end)
            : lists(begin), listsEnd(end)
        {
            if (begin != end) {
                m = (*begin)->begin();
                mEnd = (*begin)->end();
            }
        }

        const Element& operator * () const {
            return *m;
        }
        Element& operator * () {
            return *m;
        }

        bool operator != (const iterator& rhs) const {
            if (lists != rhs.lists) return true;
            if (lists == listsEnd) return false;  // m is undefined
            if (m != rhs.m) return true;
            return false;
        }

        const iterator& operator ++ () {
            ASSERT(m != mEnd);
            m++;
            if (m == mEnd) {
                ASSERT(lists != listsEnd);
                lists++;
                if (lists != listsEnd) {
                    m = (*lists)->begin();
                    mEnd = (*lists)->end();
                }
            }
            return *this;
        }
    };

 public:
    union {
        Ptr list;
        uintptr_t arrayAndFlag;
    };

    bool hasArray() const {
        return arrayAndFlag & 1;
    }

    array_t *array() const {
        return (array_t *)(arrayAndFlag & ~1);
    }

    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1;
    }

    void validate() {
        for (auto cursor = beginLists(), end = endLists(); cursor != end; cursor++)
            cursor->validate();
    }

 public:
    list_array_tt() : list(nullptr) { }
    list_array_tt(List *l) : list(l) { }
    list_array_tt(const list_array_tt &other) {
        *this = other;
    }

    list_array_tt &operator =(const list_array_tt &other) {
        if (other.hasArray()) {
            arrayAndFlag = other.arrayAndFlag;
        } else {
            list = other.list;
        }
        return *this;
    }

    uint32_t count() const {
        uint32_t result = 0;
        for (auto lists = beginLists(), end = endLists(); 
             lists != end;
             ++lists)
        {
            result += (*lists)->count;
        }
        return result;
    }

    iterator begin() const {
        return iterator(beginLists(), endLists());
    }

    iterator end() const {
        auto e = endLists();
        return iterator(e, e);
    }

    inline uint32_t countLists(const std::function & peek) const {
        if (hasArray()) {
            return peek(array())->count;
        } else if (list) {
            return 1;
        } else {
            return 0;
        }
    }

    uint32_t countLists() {
        return countLists([](array_t *x) { return x; });
    }

    const Ptr* beginLists() const {
        if (hasArray()) {
            return array()->lists;
        } else {
            return &list;
        }
    }

    const Ptr* endLists() const {
        if (hasArray()) {
            return array()->lists + array()->count;
        } else if (list) {
            return &list + 1;
        } else {
            return &list;
        }
    }

    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I];
            for (unsigned i = 0; i < addedCount; I++)
                newArray->lists[i] = addedLists[I];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; I++)
                array()->lists[i] = addedLists[I];
            validate();
        }
    }

    void tryFree() {
        if (hasArray()) {
            for (uint32_t i = 0; i < array()->count; i++) {
                try_free(array()->lists[I]);
            }
            try_free(array());
        }
        else if (list) {
            try_free(list);
        }
    }

    template
    void duplicateInto(Other &other) {
        if (hasArray()) {
            array_t *a = array();
            other.setArray((array_t *)memdup(a, a->byteSize()));
            for (uint32_t i = 0; i < a->count; i++) {
                other.array()->lists[i] = a->lists[i]->duplicate();
            }
        } else if (list) {
            other.list = list->duplicate();
        } else {
            other.list = nil;
        }
    }
}

从上面的代码我们可以发现list_array_tt方法里主要是有一个Ptr lists[0]和一个迭代器方法

 /*****删除其他代码******/ 
public:
        iterator(const Ptr *begin, const Ptr *end)
            : lists(begin), listsEnd(end)
        {
            if (begin != end) {
                m = (*begin)->begin();
                mEnd = (*begin)->end();
            }
        }

        const Element& operator * () const {
            return *m;
        }
        Element& operator * () {
            return *m;
        }
    /*****删除其他代码******/ 

从这个方法里我们可以看到是从初始值位置一直到结束值位置依次遍历并且去处其中元素,所以我们也可以这样取值,根据前面的打印接着走:

(lldb)  p $3.properties()
(const property_array_t) $5 = {
  list_array_tt = {
     = {
      list = {
        ptr = 0x0000000100008540
      }
      arrayAndFlag = 4295001408
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb)  p $5.list
(const RawPtr) $6 = {
  ptr = 0x0000000100008540
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008540
(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $8.get(0)
(property_t) $9 = (name = "zyName", attributes = "T@\"NSString\",C,N,V_zyName")
(lldb) p $8.get(1)
(property_t) $10 = (name = "zyMutArray", attributes = "T@\"NSMutableArray\",&,N,V_zyMutArray")
(lldb) 

图文解析代码:

5.png

至此,我们已经取到了类属性,层级分别是:NSObject.class -> class_data_bits_t -> class_rw_t -> property_array_t -> RawPtr -> property_list_t -> property_t下面我们可以用同样的方法取得实例方法、类方法、成员变量

实例方法:

在这之前我们也分析下方法的获取方法

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

根据代码跟踪发现下层代码和属性有些不同在于:

class method_array_t : 
    public list_array_tt
{
    typedef list_array_tt Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    const method_list_t_authed_ptr *beginCategoryMethodLists() const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr *endCategoryMethodLists(Class cls) const;
};
struct method_list_t : entsize_list_tt {
    bool isUniqued() const;
    bool isFixedUp() const;
    void setFixedUp();

    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t I = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return I;
    }

    bool isSmallList() const {
        return flags() & method_t::smallMethodListFlag;
    }

    bool isExpectedSize() const {
        if (isSmallList())
            return entsize() == method_t::smallSize;
        else
            return entsize() == method_t::bigSize;
    }

    method_list_t *duplicate() const {
        method_list_t *dup;
        if (isSmallList()) {
            dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1);
            dup->entsizeAndFlags = method_t::bigSize;
        } else {
            dup = (method_list_t *)calloc(this->byteSize(), 1);
            dup->entsizeAndFlags = this->entsizeAndFlags;
        }
        dup->count = this->count;
        std::copy(begin(), end(), dup->begin());
        return dup;
    }
};

根据属性的层次分析我们知道最终我们的得到的其实是property_t,那么我们看到上面关于方法的底层也有一个 method_t,我们跟踪进去看看:

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
   //*******省略了其他代码*******
}

发现多了一层big,big这一层就跟 property_t 一样是一个结构体

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

这样我们就知道怎么取方法了。上代码:

(lldb) x/4gx ZYPerson.class
0x1000086e0: 0x00000001000086b8 0x000000010036a140
0x1000086f0: 0x0000000101237340 0x0001803000000003
(lldb) p/x 0x1000086e0+0x20
(long) $1 = 0x0000000100008700
(lldb) p (class_data_bits_t *)0x0000000100008700
(class_data_bits_t *) $2 = 0x0000000100008700
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001012372f0
(lldb)  p $3.methods()
(const method_array_t) $4 = {
  list_array_tt = {
     = {
      list = {
        ptr = 0x0000000100008438
      }
      arrayAndFlag = 4295001144
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
(lldb) p $4.list
(const method_list_t_authed_ptr) $5 = {
  ptr = 0x0000000100008438
}
(lldb) p $5.ptr
(method_list_t *const) $6 = 0x0000000100008438
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt = (entsizeAndFlags = 27, count = 5)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "zyEatSugar"
  types = 0x0000000100003f26 "v16@0:8"
  imp = 0x0000000100003c50 (KCObjcBuild`-[ZYPerson zyEatSugar])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "zyName"
  types = 0x0000000100003f1e "@16@0:8"
  imp = 0x0000000100003c80 (KCObjcBuild`-[ZYPerson zyName])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "setZyName:"
  types = 0x0000000100003f3a "v24@0:8@16"
  imp = 0x0000000100003cb0 (KCObjcBuild`-[ZYPerson setZyName:])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "zyMutArray"
  types = 0x0000000100003f1e "@16@0:8"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[ZYPerson zyMutArray])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "setZyMutArray:"
  types = 0x0000000100003f3a "v24@0:8@16"
  imp = 0x0000000100003d00 (KCObjcBuild`-[ZYPerson setZyMutArray:])
}
(lldb) 

至此,我们已经取到了实例方法以及setter\getter方法,层级是:NSObject.class -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t_authed_ptr -> method_list_t -> big() -> method_t但是没有看到类方法和成员变量

成员变量:

我们分析struct class_rw_t {}结构体的时候并没有发现有显著标明成员变量和类方法的地方。但是我们在WWDC上有一段视频告诉我们OC/swift底层有些改变。我们根据他所说的去探索下。他提到有些东西放在了class_ro_t,那我们跟着去找找class_ro_t.同样在代码看到:

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

我们就根据set get方法来获取,代码如下:

(lldb) x/4gx ZYPerson.class
0x1000086e0: 0x00000001000086b8 0x000000010036a140
0x1000086f0: 0x0000000101223370 0x0001803000000003
(lldb) p/x 0x1000086e0+0x20
(long) $1 = 0x0000000100008700
(lldb) p (class_data_bits_t *)0x0000000100008700
(class_data_bits_t *) $2 = 0x0000000100008700
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101223190
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = {
      Value = 4295001072
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $4.ro()
(const class_ro_t *) $5 = 0x00000001000083f0
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000084b8
  Fix-it applied, fixed expression was: 
    $5->ivars
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x00000001000085a8
  name = 0x0000000100003ec3 "zyfit"
  type = 0x0000000100003f2e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
  offset = 0x00000001000085b0
  name = 0x0000000100003ec9 "zySubject"
  type = 0x0000000100003f45 "@\"NSObject\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
  offset = 0x00000001000085b8
  name = 0x0000000100003ed3 "_zyName"
  type = 0x0000000100003f2e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(3)
(ivar_t) $11 = {
  offset = 0x00000001000085c0
  name = 0x0000000100003edb "_zyMutArray"
  type = 0x0000000100003f51 "@\"NSMutableArray\""
  alignment_raw = 3
  size = 8
}

至此,我们已经取到了成员变量,层级是:NSObject.class -> class_data_bits_t -> class_rw_t -> class_ro_t -> ivar_list_t

类方法:

上代码:

(lldb) x/4gx ZYPerson.class
0x1000086e0: 0x00000001000086b8 0x000000010036a140
0x1000086f0: 0x00000001013c64f0 0x0001803000000003
(lldb) p/x 0x00000001000086b8 & 0x00007ffffffffff8
(long) $1 = 0x00000001000086b8
(lldb) x/4gx 0x00000001000086b8
0x1000086b8: 0x000000010036a0f0 0x000000010036a0f0
0x1000086c8: 0x0000000100646a20 0x0002e03100000003
(lldb) p/x 0x1000086b8+0x20
(long) $2 = 0x00000001000086d8
(lldb) p (class_data_bits_t *)0x00000001000086d8
(class_data_bits_t *) $3 = 0x00000001000086d8
(lldb) p $3->data()
(class_rw_t *) $4 = 0x00000001013c6480
(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt = {
     = {
      list = {
        ptr = 0x00000001000083d0
      }
      arrayAndFlag = 4295001040
    }
  }
}
  Fix-it applied, fixed expression was: 
    $4->methods()
(lldb) p $5.list
(const method_list_t_authed_ptr) $6 = {
  ptr = 0x00000001000083d0
}
(lldb) p $6.ptr
(method_list_t *const) $7 = 0x00000001000083d0
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $8.get(0).big()
(method_t::big) $9 = {
  name = "zyEatSomething"
  types = 0x0000000100003f26 "v16@0:8"
  imp = 0x0000000100003c20 (KCObjcBuild`+[ZYPerson zyEatSomething])
}
(lldb) 

代码图解:

6.png
7.png

至此,我们已经取到了成员变量,层级是:NSObject.class -> metaClass -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t_authed_ptr -> method_list_t -> big() -> method_t

补充:

1,内存偏移

首先我们来看一段代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        int b = 10;
        
        NSLog(@"a:%d -- %p",a,&a);
        NSLog(@"b:%d -- %p",b,&b);
        
        //对象
        ZYPerson *p1 = [[ZYPerson alloc] init];
        ZYPerson *p2 = [[ZYPerson alloc] init];
        NSLog(@"p1:%@ -- %p",p1,&p1);
        NSLog(@"p2:%@ -- %p",p2,&p2);
    }
    return 0;
}
2021-06-22 18:38:56.558556+0800 ZYProjectFour001[98912:3361252] a:10 -- 0x7ffeefbff3ac
2021-06-22 18:38:56.559010+0800 ZYProjectFour001[98912:3361252] b:10 -- 0x7ffeefbff3a8

从上面代码我们可以看到ab地址不一样,但是值是一样的,这表明他们的地址指向同一片内存值(常量10)。
对象p1,p2对象指针地址不一样p1 : 0x105a05af0p2 : 0x105a05b60,并且指向的空间也不同分别为:0x7ffeefbff3a00x7ffeefbff398
所以,他们的指针以及指向内存的情况应该是如下图:

内存偏移.png

接下来我们再看一组代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //数组指针
        int c[4] = {1,2,3,4};
        int *d = c;
        NSLog(@"c:%p -- %p -- %p",&c,&c[0],&c[1]);
        NSLog(@"d:%p -- %p -- %p",d,d+1,d+2);
        
        for (int i = 0; i<4; i++) {
            int value = c[I];
            NSLog(@"value == %d",value);
            NSLog(@"value2 == %d",*(d+i));
        }
    }
    return 0;
}
2021-06-22 18:38:56.559451+0800 ZYProjectFour001[98912:3361252] c:0x7ffeefbff3c0 -- 0x7ffeefbff3c0 -- 0x7ffeefbff3c4
2021-06-22 18:38:56.559490+0800 ZYProjectFour001[98912:3361252] d:0x7ffeefbff3c0 -- 0x7ffeefbff3c4 -- 0x7ffeefbff3c8
2021-06-22 18:38:56.559547+0800 ZYProjectFour001[98912:3361252] value == 1
2021-06-22 18:38:56.559593+0800 ZYProjectFour001[98912:3361252] value2 == 1
2021-06-22 18:38:56.559627+0800 ZYProjectFour001[98912:3361252] value == 2
2021-06-22 18:38:56.559658+0800 ZYProjectFour001[98912:3361252] value2 == 2
2021-06-22 18:38:56.559690+0800 ZYProjectFour001[98912:3361252] value == 3
2021-06-22 18:38:56.559721+0800 ZYProjectFour001[98912:3361252] value2 == 3
2021-06-22 18:38:56.559752+0800 ZYProjectFour001[98912:3361252] value == 4
2021-06-22 18:38:56.559781+0800 ZYProjectFour001[98912:3361252] value2 == 4

从上面的代码我们发现 &c&c[0] 是同一个地址0x7ffeefbff3c0
并且我们发现 dd+1的地址又和&c&c[1] 相同。所以他们指向的是同一片内存。d -> d+1 -> d+2这种方式就是把数组指针从第一个元素移动到第二个第三个元素的地址。这就是指针平移

本章内容就算是告一段落,下一章我们继续。

遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!

你可能感兴趣的:(类的原理分析(上))