iOS底层原理07:类的底层原理探索(2)

381 阅读2分钟

前言

上一篇iOS底层原理06:类的底层原理探索(1)已经对类的底层原理有了初步的探索,这篇文章将继续探索。

前期准备

定义两个类

  • Person继承自NSObject
//import "Person.h
@interface Person : NSObject{
    NSString *hobby;
}

@property (nonatomic,copy) NSString *name;
- (void)eat;
+ (void)run;
@end

//import "Person.m
@implementation Person
- (void)eat
{}
+ (void)run
{}
@end

  • Teacher继承自Person
@interface Teacher : Teacher
@end

@implementation Teacher
@end
  • 在main中创建两个对象:p1t1
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...        
        Person *p1 = [Person alloc];
        Teacher *t1 = [Teacher alloc];
        MyNSLog(@"%@ --%@ ", p1,t1);
   }
    return 0;
}

探索 属性列表

上一篇文章iOS底层原理06:类的底层原理探索(1),我们探索到了class_rw_t,并且在源码中发现,结构体中有提供相应的方法去获取 属性列表方法列表等,如下所示 我们现在将lldb调试到上一篇内容的位置 lldb执行p $3.properties()

(lldb) p $3.properties()
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100003240
      arrayAndFlag = 4294980160
    }
  }
}

通过list的地址获取信息,执行p *$4.list

(lldb) p *$4.list
(property_list_t) $5 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}

执行p $5.get(0)获取第一个属性

(lldb) p $5.get(0)
(property_t) $6 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")

执行p $5.get(1)获取第二个属性,发现lldb报数组越界的错误信息。 说明明 property_list 中只有 一个属性name。 探索的过程如下图

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

小结

类的属性存储在bits中,通过通过bits --> data() --> properties() --> list获取。

探索 方法列表

我们仿照属性列表的探索,在lldb下调试

  • 通过执行 p $3.methods() 获得具体的方法列表的内部结构,methods()class_rw_t提供的方法。

  • count = 4可知,方法列表中存储了4个方法。因此获取第5个方法是,报数组越界的错误。

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

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

小结

类的实例方法存储在类的bits中,通过bits --> methods() --> list获取实例方法列表,类中的方法列表存储了实例方法,属性的set方法get方法

探索 类方法存储

我们在方法列表的探索中发现,前面定义的类方法run()并没有存在在方法列表中,那么类方法存储在哪呢?

iOS底层原理05:isa底层原理探索下文章中,我们得知,类对象(classisa 指向 元类(Meta class),元类存储的类的信息,那么在元类的bits中是否存储了类方法呢?我们继续通过lldb调试。

小结

类的类方法存储在元类的bits中,通过元类bits --> data() --> methods() --> list获取类方法列表。

探索 成员变量

通过上面的探索,我们知道了属性方法类方法的存储,那么成员变量又存储在哪呢?

在的源码中,发现还有个ro()方法,那么我们猜测,成员变量会不会存在这里呢?

通过lldb调试

获取第一个数据

(lldb) p $7.get(0)
(ivar_t) $9 = {
  offset = 0x0000000100003290
  name = 0x0000000100001e91 "hobby"
  type = 0x0000000100001f79 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
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;
    }
};

小结

类的成员变量以结构体ivar_t的形式存储在bits中,通过通过bits --> data() -->ro() --> ivars获取。