iOS 类的底层探索(下)

118 阅读2分钟

之前的篇章我们通过objc源码对对象方法属性的存储进行了探究,得到的结论是存在类的class_data_bits_tclass_rw_t里面,今天我们继续探索一下类方法成员变量 的存储。

成员变量的存储位置

首先,我们先看段代码

@interface LGPerson : NSObject{
    NSString *_hobby;
    NSString *_nickName;
}

@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;

- (void)sayInstanceMethod;
+ (void)sayClassMethod;
+ (void)sayGood;

@end

接下来我们就通过对源码的分析以及lldb的调试来探索一下成员变量的存储位置:

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_data_bits_t

struct class_data_bits_t {
...
 class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
...
}

查看class_data_bits_t的数据结构,里面有一个ro()方法能够得到 class_ro_t

struct class_rw_t {
...
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }
...
}

class_ro_t 的数据结构,在我们的开发中,成员变量一般都是用ivar来表示,所以我们来看下ivar_list_t

struct class_ro_t {
    ...
    
    explicit_atomic<const char *> name;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    ...
};

ivar_list_t继承entsize_list_tt,继续跟踪

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};

我们可以把entsize_list_tt看成一个模板,通过这个模板里的get()方法我们就能获取到ivar_t

template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    ...
    
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }

    ...
};

看下 ivar_t的数据结构

struct ivar_t {
...
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;
...
};

以上是通过源码分析,接下来我们通过lldb指令来看一下流程

521651323203_.pic.jpg

总结:通过上面的源码和lldb调试,我们能够打印出_hobby_nickName_age_name成员变量,因此我们能够知道真正的成员变量是存放在class_ro_t的结构体中。

一个小问题:为什么我们的真正的成员变量存放在类里面,而成员变量的值存放在实例对象里面?

因此,我们可以这样理解,类的本质是一个结构体,那么结构体可以看做是一个模板,这个模板里面就有我们的成员变量、方法、协议、属性等一些内容,我们的实例对象就是根据这个模板生成的,那么我们在创建不同的实例对象时他们的成员变量的值是不一样的,所以就把不一样的值存放在不一样的对象里面就可以了。

类方法的存储位置

我们在上面的代码里写了两个类方法+ (void)sayClassMethod
+ (void)sayGood,我们可能都知道类方法是存放在元类中,那我们就来看一下是否真的存放在元类中。

我们通过lldb来调试一下,首先要得到LGPerson元类,然后内存平移得到class_data_bits_t,如图所示

图片.png

如上图所示,我们确实在LGPerson元类的方法列表中打印出这两个方法。

总结

成员变量存储在roivars里面。
类方法存储在元类中。