类,类结构分析

2020-11-04  本文已影响0人  丸疯

忙不是不学习的借口

isa和类的关联中我们知道isa中存储着类信息,今天我们就来探索一下类与类的结构。

准备工作

//.h文件
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject{
    NSString *name;
}
@property (nonatomic, strong) NSString * hobby;

+ (void)sayHello;
- (void)sayGood;

@end

//.m文件
#import "Person.h"

@implementation Person
+ (void)sayHello {
    
}
- (void)sayGood {
    
}
@end
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Student : Person

@end

NS_ASSUME_NONNULL_END

#import "Student.h"

@implementation Student

@end

元类

通过上篇文章,我们知道isa & ISA_MASK可以得到类信息。
我们实例化一个 Person对象kevin,来摸一下isa研究一下。

(lldb) p/x kevin
(Person *) $0 = 0x00000001004b2180
(lldb) po 0x00000001004b2180
<Person: 0x1004b2180>

(lldb) x/4gx 0x00000001004b2180
0x1004b2180: 0x001d80010000848d 0x0000000000000000
0x1004b2190: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d80010000848d
-dßd�

(lldb) p/x 0x001d80010000848d & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100008488
(lldb) po 0x0000000100008488
Person

(lldb) x/4gx 0x0000000100008488
0x100008488: 0x0000000100008460 0x00007fff9497d118
0x100008498: 0x0000000100407610 0x0004802400000007
(lldb) po 0x0000000100008460
Person

(lldb) p/x 0x0000000100008460 & 0x00007ffffffffff8ULL
(unsigned long long) $6 = 0x0000000100008460
(lldb) po 0x0000000100008460
Person

(lldb) x/4gx 0x0000000100008460
0x100008460: 0x00007fff9497d0f0 0x00007fff9497d0f0
0x100008470: 0x0000000100705bb0 0x0003e03500000007
(lldb) po 0x00007fff9497d0f0
NSObject

(lldb) p/x 0x00007fff9497d0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00007fff9497d0f0
(lldb) po 0x00007fff9497d0f0
NSObject

(lldb) x/4gx 0x00007fff9497d0f0
0x7fff9497d0f0: 0x00007fff9497d0f0 0x00007fff9497d118
0x7fff9497d100: 0x000000010050f270 0x0004e03100000007
(lldb) po 0x00007fff9497d0f0
NSObject

lldb调试:

  1. p/x kevin获取对象在内存中的首地址
    • 打印首地址po 0x00000001004b2180,我们得到了一个指向Person的指针地址0x1004b2180
  2. x/4gx 0x00000001004b2180获取kevin的内存情况
    • 拿到kevin对象的isa 0x001d80010000848d
  3. 我们将kevin对象的类信息isa(0x001d80010000848d) & 0x00007ffffffffff8ULL
    • 打印类信息的首地址(0x0000000100008488)
    • 对首地址进行po,我们得到Person,说明首地址指向的是内存中的Person,也印证了isa中的shiftcls中存放着类信息。
  4. 我们再读取Person类的内存情况(x/4gx 0x0000000100008488)
    • Person的isa(0x0000000100008460)进行po,发现直接打印出了Person,这里大家不免就有疑惑了,0x00000001000084880x0000000100008460明明指向的不是同一片内存为啥会都打印出Person。继续往下分析...
  5. 我们既然拿到的Person类的isa(0x0000000100008460),我们再对类进行类信息获取(p/x 0x0000000100008460 & 0x00007ffffffffff8ULL)并打印出首地址(0x0000000100008460)
    • 我们发现在第4步得到的Person的类的isa(0x0000000100008460)与我们现在获取到的类信息地址是完全一致的。0x00000001000084880x0000000100008460指向的不是同一内存区域,却打印出了"同一结果"?
    • 这里引出元类0x0000000100008488指向的是Person类,0x0000000100008460指向的是Person的元类元类底层源码自动生成,每个类都会对应一个元类既然有了元类,我们继续摸元类的isa...
  6. 获取Person元类的内存情况x/4gx 0x0000000100008460
    • 打印其首地址po 0x00007fff9497d0f0,我们居然得到了NSObject
    • 继续对NSObjectisa,发现无论是首地址还是其isa都是同一个地址0x00007fff9497d0f0。那这个NSObject是不是就是我们所熟悉的那个NSObject呢?留在后面验证...
      isa走位图.png
      通过上面的分析,是不是就印证了经典的isa走位图。按图上的isa走位我们知道了,最后isa的走位一直指向0x00007fff9497d0f0其实就是根元类。那为什么会打印出NSObject呢?印证与系统的isa是不是同一个
(lldb) p/x NSObject.class
(Class) $12 = 0x00007fff9497d118 NSObject
(lldb) x/4gx 0x00007fff9497d118
0x7fff9497d118: 0x00007fff9497d0f0 0x0000000000000000
0x7fff9497d128: 0x00000001004b23c0 0x0001801000000003
(lldb) po 0x00007fff9497d118
NSObject

(lldb) po 0x00007fff9497d0f0
NSObject

(lldb) p/x 0x00007fff9497d0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00007fff9497d0f0

分析

  1. 获取NSObject在内存中的首地址p/x NSObject.class得到0x00007fff9497d118
  2. 获取NSObject的内存分布情况x/4gx 0x00007fff9497d118
    • 得到isa(0x00007fff9497d0f0),此时的isa和首地址po出来都是NSObject,结合上面的分析,0x00007fff9497d0f0其实是NSObject的元类,并且内存地址和上面的内存地址是一致的,继续摸isa也是同一个地址。由于NSObject是根类,所以此时的0x00007fff9497d0f0根元类的地址,印证了类在内存中只有一份且再次印证上图。

继承关系走位图

继承关系走位图.png
结合两幅图,就得到了 继承关系及isa走位图.png
现在我们明白了对象,类,元类,根元类的关系,那么为什么无论是对象,还是类都有isa呢?

objc_class & objc_object

__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa __attribute__((deprecated));
} __attribute__((unavailable));

我们由此,猜想是不是所有的Class都是以objc_class为模板来创建的。
objc4-781源码中搜索objc_class

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

这里我们可以看到,在OBJC2已经废弃了,从注释我们也可以看出现在用Class代替了struct objc_class *,是不是这里也可以说明Class都是以struct objc_class为模板创建的。继续往下找。

struct objc_class : objc_object {
    // 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();
    }
    ....//省略部分代码
}
struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;
    ...//省略部分代码
}

这里是不是大家就非常熟悉的看到了objc_class的继承关系,原来无论在老版还是新版objc_class都继承自objc_object

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

objc_object是一个结构体,其中包含了一个isa

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
...
}

我们看到objc_object其中包含一个isaobjc_class又是继承于objc_object
结论

类结构分析

我们在上面的探索中得出了类都是以objc_class为模板创建的,那么类的结构到底如何

struct objc_class : objc_object {
    // 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();
    }
    ....//省略部分代码
}
cache的内存大小

进入cache_t类型的内部

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

这里只贴出影响大小的属性,一些staic修饰的属性,不占大小,故不做大小分析

类bits探索

(lldb) p/x Person.class
(Class) $0 = 0x00000001000080e8 Person
(lldb) p/x 0x00000001000080e8 + 32
(long) $1 = 0x0000000100008108
(lldb) p (class_data_bits_t *)0x0000000100008108
(class_data_bits_t *) $2 = 0x0000000100008108

这里我们知道首地址偏移32位是class_data_bits_t类型的,所有直接p此时的内存地址并强转为class_data_bits_t类型,没有报错,进一步论证此时的内存地址就是存储着bits

(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001011890e0

通过$2->data()去获取class_rw_t,这里可以从两个地方论证

  1. objc_class的结构中,我们可以清晰的看到bits.data()
    objc_class结构.png
  2. 进入class_data_bits_t的内部我们也能发现,class_data_bits内部有data()的get方法,还有set方法
    class_data_bits内部有data()的get方法,还有set方法.png
    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 *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }

methods():方法列表

  1. 获取data()数据信息
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000208
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

  1. 获取方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000080d8
      arrayAndFlag = 4295000280
    }
  }
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000080d8
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003e10 (KCObjc`-[Person sayHello])
    }
  }
}
  1. 对方法列表进行单个输出
(lldb) p $6.get(0)
(method_t) $7 = {
  name = "sayHello"
  types = 0x0000000100003f81 "v16@0:8"
  imp = 0x0000000100003e10 (KCObjc`-[Person sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
  name = "bobby"
  types = 0x0000000100003f95 "@16@0:8"
  imp = 0x0000000100003e20 (KCObjc`-[Person bobby])
}
(lldb) p $6.get(2)
(method_t) $9 = {
  name = "setBobby:"
  types = 0x0000000100003f9d "v24@0:8@16"
  imp = 0x0000000100003e40 (KCObjc`-[Person setBobby:])
}
(lldb) p $6.get(3)
(method_t) $10 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f81 "v16@0:8"
  imp = 0x0000000100003e70 (KCObjc`-[Person .cxx_destruct])
}
(lldb) p $6.get(4)
Assertion failed: (i < count), function get, 
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

这里我们看到有4个方法,.cxx_destructsayHello,还有bobbygetset方法,为啥没有我们的类方法sayNB呢?

properties():属性列表

探索方法,同上,这里我们也会发现,没有成员边量,只有我们的属性。

成员变量的存储

刚刚我们在properties()中未发现成员变量bobby,那个成员变量存在哪里呢?
通过探索,我们在class_rw_t的结构中发现了一个ro()ro()的类型是``,进入其内部

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;

取到ro()中的ivars,然后就可以看到我们的成员变量,和属性都在里面

(lldb) p $16.ivars
(const ivar_list_t *const) $19 = 0x0000000100008140
(lldb) p *$19
(const ivar_list_t) $20 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000081a8
      name = 0x0000000100003f4e "name"
      type = 0x0000000100003f89 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

类方法的存储

我们在Person类的方法列表中,没有找到类方法。我们在元类的方法列表中发现了Person的类方法,由此我们可知,类的实例方法是存在类的bits里面。类的类方法是存放在元类的bits里面

上一篇下一篇

猜你喜欢

热点阅读