类的方法属性探究上

2021-06-18  本文已影响0人  Kates

今天来探索一下类的成员方法和成员属性以及类方法都存放在哪里

Class的本质

首先先来补充一下类的本质Class,通过源码知道

typedef struct objc_class *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
}

struct objc_object {
private:
    isa_t isa
};

struct objc_class 继承了objc_object, 所以objc_class 也有isa指针,
接下来我们要研究的是bits这个属性,为什么要研究这个bits呢,还有怎么去拿到bits呢?

Bits的获取

为什么要研究这个bits呢?
还记得之前研究对象申请空间大小是看到过这样一段代码吗?这两个函数就是在struct objc_class 的成员函数,这样我们就可以顺藤摸瓜成员变量应该就在这个bits里面,方法可能也在里面

uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize; // 这边返回的是对象所有成员变量的大小
}

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

怎么去拿到bits呢?
首先我们想到的是通过指针平移来获取,如果平移的话我们就要先确定需要平移多少位,在bits前面有isa,superclass,cache,isa 8字节,superclass 8字节,cache?
我们来看看cache结构体

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}

实际的cache_t结构体非常庞大,但是呢大部分都是方法和静态方法,所以我把他去掉只剩上面,由此可以看出cache_t 的大小只有16字节也就是我们需要平移32字节
接下来我们通过lldb来研究查看

@interface HFObject : NSObject
{
    NSString *_a;
    NSString *_b;
}

@property (nonatomic,strong) NSString *name;

@property (nonatomic,strong) NSString *nickName;

- (void)hello;

+ (void)world;

@end

HFObject *p = [HFObject alloc];
NSLog(@"%@",p);

(lldb) x/4gx HFObject.class
0x100004650: 0x0000000100004628 0x0000000100369140
0x100004660: 0x00000001003603c0 0x0000803000000000
打印HFObject的类地址,我们已知需要便宜32字节也就是0x20到bits位置
(lldb) p/x 0x100004650+32
(long) $2 = 0x0000000100004670
这样似乎看不出什么来
(lldb) p (class_data_bits_t *)0x0000000100004670
(class_data_bits_t *) $3 = 0x0000000100004670
这样就得到了class_data_bits_t 指针
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000101034140
我们进入到class_rw_t 结构体里惊奇的发现里面有几个函数
  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};
        }
    }

methods会不会是方法列表呢?
properties 会不会是属性呢?
protocols 会不会是协议呢?
一个个来探索一下
(lldb) p $4->properties()
(const property_array_t) $8 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000043c0
      }
      arrayAndFlag = 4294984640
    }
  }
}
(lldb) p $8.list.ptr
(property_list_t *const) $9 = 0x00000001000043c0
(lldb) p *$9
(property_list_t) $10 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $10.get(0)
(property_t) $11 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $10.get(1)
(property_t) $12 = (name = "nickName", attributes = "T@\"NSString\",&,N,V_nickName")
这边我们发现property存放的是成员属性,但是成员变量不再里面
继续探究methods方法
(lldb) p $4->methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000042b8
      }
      arrayAndFlag = 4294984376
    }
  }
}
(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x00000001000042b8
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 5)
}
(lldb) p $7.get(0).big()
(method_t::big) $14 = {
  name = "hello"
  types = 0x0000000100003f7e "v16@0:8"
  imp = 0x0000000100003c30 (KCObjcBuild`-[HFObject hello])
}
果然没错,methods存放的确实是成员方法,但是类方法却不再里面
之前我们计算对象大小时是用的data()->ro()->instanceSize;
接下来我们看看ro()
(lldb) p $4->ro()
(const class_ro_t *) $17 = 0x0000000100004270
(lldb) p $17->ivars
(const ivar_list_t *const) $18 = 0x0000000100004338
(lldb) p *$18
(const ivar_list_t) $19 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $19.get(0)
(ivar_t) $20 = {
  offset = 0x00000001000045a8
  name = 0x0000000100003f1d "_a"
  type = 0x0000000100003f6a "@\"NSString\""
  alignment_raw = 3
  size = 8
}
果然ro里面存放着所有的成员变量

还有一个问题,类方法放在那里呢?,因为我们在这边methods里面并没有看到,对象的方法属性放在类里面,那类的方法是不是放在元类里面呢?
继续lldb看看

(lldb) x/4gx HFObject.class
0x100004650: 0x0000000100004628 0x0000000100369140
0x100004660: 0x00000001003603c0 0x0000803000000000
(lldb) p (class_data_bits_t *)0x0000000100004648
(class_data_bits_t *) $25 = 0x0000000100004648
(lldb) p $25->data()
(class_rw_t *) $26 = 0x0000000101034120
(lldb) p $26->methods()
(const method_array_t) $27 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100004250
      }
      arrayAndFlag = 4294984272
    }
  }
}
(lldb) p $27.list.ptr
(method_list_t *const) $28 = 0x0000000100004250
(lldb) p *$28
(method_list_t) $29 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $29.get(0).big()
(method_t::big) $30 = {
  name = "world"
  types = 0x0000000100003f7e "v16@0:8"
  imp = 0x0000000100003c20 (KCObjcBuild`+[HFObject world])
}
果然在元类里面
上一篇 下一篇

猜你喜欢

热点阅读