之前的篇章我们通过objc源码对对象方法和属性的存储进行了探究,得到的结论是存在类的class_data_bits_t 的class_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指令来看一下流程
总结:通过上面的源码和lldb调试,我们能够打印出_hobby、_nickName、_age、_name成员变量,因此我们能够知道真正的成员变量是存放在class_ro_t的结构体中。
一个小问题:为什么我们的真正的成员变量存放在类里面,而成员变量的值存放在实例对象里面?
因此,我们可以这样理解,类的本质是一个结构体,那么结构体可以看做是一个模板,这个模板里面就有我们的成员变量、方法、协议、属性等一些内容,我们的实例对象就是根据这个模板生成的,那么我们在创建不同的实例对象时他们的成员变量的值是不一样的,所以就把不一样的值存放在不一样的对象里面就可以了。
类方法的存储位置
我们在上面的代码里写了两个类方法+ (void)sayClassMethod
+ (void)sayGood,我们可能都知道类方法是存放在元类中,那我们就来看一下是否真的存放在元类中。
我们通过lldb来调试一下,首先要得到LGPerson元类,然后内存平移得到class_data_bits_t,如图所示
如上图所示,我们确实在LGPerson元类的方法列表中打印出这两个方法。
总结
成员变量存储在ro的ivars里面。
类方法存储在元类中。