类的结构分析

我们从上面一个章节isa初始化&指向分析已经完美的从对象过渡到了类,接下来我们开始对类进行探索。

在开始探索之前我们先了解一下下面的内容,主要是为了讲解后面的类的结构体:

int array[] = {1,2,4};
int *b = array;
NSLog(@"%p-%p-%p",&array,&array[0],&array[1]);
NSLog(@"%p-%p-%p",b,b+1,b+2);
        
for (int i = 0; i < 3; i++) {
      int value = *(b+i);
     NSLog(@"value-%d",value);
}

2019-12-21 21:06:11.914923+0800 XDTest[2663:291951] 0x7ffeefbff5bc-0x7ffeefbff5bc-0x7ffeefbff5c0
2019-12-21 21:06:11.915605+0800 XDTest[2663:291951] 0x7ffeefbff5bc-0x7ffeefbff5c0-0x7ffeefbff5c4
2019-12-21 21:06:11.915673+0800 XDTest[2663:291951] value-1
2019-12-21 21:06:11.915729+0800 XDTest[2663:291951] value-2
2019-12-21 21:06:11.915756+0800 XDTest[2663:291951] value-4

说明指针b的地址就是array的首地址,同时我们可以通过指针偏移可以指向接下来连续的内存地址。

类的结构分析

typedef struct objc_class *Class;
struct objc_class : objc_object{};

从源码中可以看到类Class的结构就是objc_class结构体。
而objc_class继承自objc_object,证明了我们万物皆对象的这句话。

抛出下面两个问题

  1. objc_class与NSObject的关系?
    NSObject本身是一个类,在底层实现就是objc_class。
  2. objc_object与NSObject的关系?
    objc_object是c的结构类型,NSObject是OC的类型,
    NSObject就是对objc_object的封装。

分析objc_class结构体

struct objc_class : objc_object {
      // Class ISA;            //8
    Class superclass;      //8
    cache_t cache;          //16        // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    ...省略其他的信息...
};
  1. 第一个属性 Class ISA 被注释掉的,意思就是从父类继承过来的,我们进入objc_object里面可以看到,占用8个字节。
struct objc_object {
private:
    isa_t isa;
  ...省略其他的信息...
};
  1. 第二个属性Class superclass父类,占用8个字节。
  2. 第三个属性cache_t cache一个结构体,顾名思义是一些缓存的信息,总共占用16个字节
struct cache_t {
    struct bucket_t *_buckets;  //指针占用8字节
    mask_t _mask;               // int32  占用4字节
    mask_t _occupied;           //占用4字节

 ...省略其他的信息...
};
  1. 第四个属性class_data_bits_t bits也是一个结构体。

属性-方法的归属探索
我们先定义一个类的属性,成员变量,实例方法,类方法,然后去探索

@interface XDPerson : NSObject
{
    NSString *otherName;
}
@property (nonatomic, copy) NSString *nickName;

- (void)sayHello();

+ (void)sayHappy();
@end

通过objc_class结构体,我们可以分析一下,isa,superclass,cache这三个属性可以明确看出来,没有我们需要查找的信息,那么我们的目标就要对准bits这个属性了。

在本文最开始讲解了一个简单的内存偏移,目的就是通过类的内存地址就是内存段的首地址,然后通过偏移去找到我们的目标bits的地址。

这里我们直接把结论给拿出来,然后通过查看内存信息再来验证。

  1. 获取类的相关信息,用下面的这个函数,返回的是class_rw_t这么一个结构体
class_rw_t *data() { 
        return bits.data();
    }
  1. 查看class_rw_t结构体,我们似乎看到了我们熟悉的属性,方法,代理等相关的数据类型。
    但是这里要注意了,我们类的属性,方法并没有存在method_array_t、property_array_t这些类型的属性里面(它是什么,后面章节会介绍)。
    而是存放在了class_ro_t这个结构体里面,我们看到定义的是const,可以说明这一块会在编译时候就确定好了,后面取出来使用是不可以更改的。
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;

...省略其他的信息...
};
  1. 查看class_ro_t
    我们的属性在baseProperties,成员变量在** ivars,方法在 method_list_t * baseMethodList**等这些位置放着。
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;
   ...省略其他的信息...

接下来开始我们的lldb探索之路,分别查看class_ro_t结构体里面的属性的内容:

  1. 查看类XDPerson的内存信息
(lldb) x/4gx XDPerson.class
0x100001318: 0x001d8001000012f1 0x0000000100aff140
0x100001328: 0x000000010203ce00 0x0000000200000003
  1. 我们知道信息在objc_class结构体的bits属性里面,通过内存地址偏移来找到bits的内存地址,前面我们已经把bits之前属性所占的字节数全部标出来了,我们可以直接去找0x100001318+32个字节=0x100001338
(lldb) p (class_data_bits_t *)0x100001338
(class_data_bits_t *) $1 = 0x0000000100001338
  1. 寻找class_rw_t通过bits.data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010203cd50
  1. 查看class_rw_t内存数据信息
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148139008
  version = 0
  ro = 0x00000001000011f0
  methods = {
    list_array_tt = {
       = {
        list = 0x0000000100001128
        arrayAndFlag = 4294971688
      }
    }
  }
  properties = {
    list_array_tt = {
       = {
        list = 0x00000001000011d8
        arrayAndFlag = 4294971864
      }
    }
  }
  protocols = {
    list_array_tt = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000 
}
  1. 我们知道我们的目标是在ro里面
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x00000001000011f0
  1. 查看class_ro_t的内存数据信息,可以明确的看到我们相关的属性的值
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000f80 "\x02"
  name = 0x0000000100000f77 "XDPerson"
  baseMethodList = 0x0000000100001128
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001190
  weakIvarLayout = 0x0000000000000000 
  baseProperties = 0x00000001000011d8
  _swiftMetadataInitializer_NEVER_USE = {}
}
  1. 查看baseProperties,发现只有一个属性名称nickName,和我们定义的相同
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x00000001000011d8
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "nickName", attributes = "T@"NSString",C,N,V_nickName")
  }
}
  1. 查看ivars,它有两个成员变量otherName_nickName
(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x0000000100001190
(lldb) p *$8
(const ivar_list_t) $9 = {
  entsize_list_tt = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000012e8
      name = 0x0000000100000f20 "otherName"
      type = 0x0000000100000fa7 "@"NSString""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $9.get(0)
(ivar_t) $10 = {
  offset = 0x00000001000012e8
  name = 0x0000000100000f20 "otherName"
  type = 0x0000000100000fa7 "@"NSString""
  alignment_raw = 3
  size = 8
}
(lldb) p $9.get(1)
(ivar_t) $11 = {
  offset = 0x00000001000012e0
  name = 0x0000000100000f2a "_nickName"
  type = 0x0000000100000fa7 "@"NSString""
  alignment_raw = 3
  size = 8
}
  1. 查看baseMethodList,可以看到我们的属性生成了setter、getter方法,一个c++的析构函数、还有我们的一个实例方法sayHello
(lldb) p $5.baseMethodList
(method_list_t *const) $12 = 0x0000000100001128
(lldb) p *$12
(method_list_t) $13 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f8c "v16@0:8"
      imp = 0x0000000100000d70 (XDTest`-[XDPerson sayHello] at XDPerson.m:11)
    }
  }
}
(lldb) p $13.get(0)
(method_t) $14 = {
  name = "sayHello"
  types = 0x0000000100000f8c "v16@0:8"
  imp = 0x0000000100000d70 (XDTest`-[XDPerson sayHello] at XDPerson.m:11)
}
(lldb) p $13.get(1)
(method_t) $15 = {
  name = "nickName"
  types = 0x0000000100000f94 "@16@0:8"
  imp = 0x0000000100000d90 (XDTest`-[XDPerson nickName] at XDPerson.h:16)
}
(lldb) p $13.get(2)
(method_t) $16 = {
  name = "setNickName:"
  types = 0x0000000100000f9c "v24@0:8@16"
  imp = 0x0000000100000dc0 (XDTest`-[XDPerson setNickName:] at XDPerson.h:16)
}
(lldb) p $13.get(3)
(method_t) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f8c "v16@0:8"
  imp = 0x0000000100000e00 (XDTest`-[XDPerson .cxx_destruct] at XDPerson.m:10)
}
  1. 看到这里我们回想一下我们还定义了一个类方法sayHappy,我们没有看到这个类方法,我们这时候可能会unhappy了。那我们还有一个类方法跑去哪里了呢?
    其实我们通过上一章节isa初始化&指向分析了解到我们的类在它的元类里面相当于一个对象,我们叫类对象,那我们的类的类方法会不会在它的元类里面相当于一个实例方法呢。
...省略部分调试 去查找元类的相关信息...
(lldb) p $25.baseMethodList
(method_list_t *const) $26 = 0x00000001000010c0
(lldb) p *$26
(method_list_t) $27 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100000f8c "v16@0:8"
      imp = 0x0000000100000d80 (XDTest`+[XDPerson sayHappy] at XDPerson.m:15)
    }
  }
}

我们的sayHappy类方法在元类里面找到了。
到这里为止我们自定义的类的属性,成员变量,实例方法,类方法都找到了。
我们通过上面的调试可以继续学习一下下面的几个结构体

  1. property_t结构体
struct property_t {
    const char *name;
    const char *attributes;
};
  1. ivar_t结构体
struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms.
    // We read and write only 32 bits of it.
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values.
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit.
#endif
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};
  1. method_t结构体
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;  //using MethodListIMP = IMP;

    struct SortBySELAddress :
        public std::binary_function
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

其实我们会思考我们继续定义一个protocol,它是否会存在class_ro_t的结构体里面呢,这里可以通过上面的lldb调试去尝试,最后的结果会出乎我们的意料,关于原因,我会在后面的章节中继续讲解。

你可能感兴趣的:(类的结构分析)