在上一篇初识isa文章中,我们提到对象的方法是存储在类中,类方法是存储在元类中
struct objc_class : objc_object {
// Class ISA;
Class superclass;//0x18
cache_t cache; //0x20 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
isa(继承自objc_object)指向元类superclass指向父类cache方法缓存,当调用一次方法后就会缓存进vtable中,加速下次调用bits这是今天的主角,就是存储类的方法、属性和遵循的协议等信息的地方(rr/alloc flags是什么意思?哪位大神麻烦帮解释下)
class_data_bits_t结构体
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
}
这么一看,结构体里面只有一个bits?在objc_class结构体中的注释写到 class_data_bits_t 相当于 class_rw_t 指针加上 rr/alloc 的标志(再次求教各位大神rr/alloc是什么意思)。它为我们提供了简单的方法返回class_rw_t *指针
#define FAST_DATA_MASK 0x00007ffffffffff8UL
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
FAST_DATA_MASK转成二进制是
0000 0000 0000 0000 0111 1111 1111 1111
1111 1111 1111 1111 1111 1111 1111 1000
可以看出class_rw_t *位置在[3,47]
bool getBit(uintptr_t bit)
{
return bits & bit;
}
bool isSwift() {
return getBit(FAST_IS_SWIFT);
}
bool hasDefaultRR() {
return getBit(FAST_HAS_DEFAULT_RR);
}
bool instancesRequireRawIsa() {
return getBit(FAST_REQUIRES_RAW_ISA);
}
#define FAST_IS_SWIFT (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR (1UL<<1)
/*class's instances requires raw isa*/
#define FAST_REQUIRES_RAW_ISA (1UL<<2)
可以看出后三位标识
- isSwift()
FAST_IS_SWIFT第一位,标识是不是swift类
- hasDefaultRR()
FAST_HAS_DEFAULT_RR第二位,判断当前类或者父类含有默认的retain/release/autorelease/retainCount/ _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法
- instancesRequireRawIsa()
FAST_REQUIRES_RAW_ISA第三位,当前类是否使用纯指针表示
执行class_data_bits_t中的data()方法,或者objc_class中的data()方法都会返回一个class_rw_t*指针,因为,objc_class中的方法就是对class_data_bits_t的封装
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
class_rw_t 与 class_ro_t
OC中所用到的方法,协议等都存储在class_rw_t中
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
其中还含有一个const class_ro_t *ro常量,存放了编译就确定了的属性/成员变量/方法等(属性/成员变量应该不止编译期间的,还有使用runtime添加的属性,成员变量都是在这里面查看了下class_addIvar([XXObject class], "yty_test", sizeof(int), 0, "i");在源码中的确会更改ro,但是对我们生成的类没有效果,只有我们使用runtime创建的类才有效果)
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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
在编译期间类结构体中class_data_bits_t * bits的data()中存放的是class_ro_t指针,在objc源码中可以找到static Class realizeClass(Class cls)方法
static Class realizeClass(Class cls)
{
runtimeLock.assertWriting();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
ro = (const class_ro_t *) cls->data();//编译期间存放的是class_ro_t
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
}
该方法太长,只截取了一部分
这个方法很简单
- 从
class_data_bits_t调用data方法,将结果从class_rw_t强制转换为class_ro_t指针 - 初始化一个
class_rw_t结构体 - 设置结构体
ro的值以及flag - 最后设置正确的
data。
这个方法调用后,类中就有了class_rw_t了,但是这时候所有的方法,属性,协议都还是空,这时候就会调用static void methodizeClass(Class cls)方法将自己本身存在的,分类中存在的都加载进class_rw_t
想要验证编译期间data()是class_ro_t,可以参考深入解析 ObjC 中方法的结构
realizeClass
该方法是在类第一次初始化的时候分配可读写数据空间并返回真正的类结构,在返回出去的的类结构中包含了,所有自身,分类中的方法,属性,协议
方法的结构
方法也是个结构体
struct method_t {
SEL name;
const char *types;
IMP imp;
}
- name 标识方法名字
- types 方法类型(eg:
[XXObject hello]的方法类型就是v16@0:8) - imp 函数指针,真正调用的就是这个
最后让我引用Draveness大神的总结
- 类在内存中的位置是在编译期间决定的,在之后修改代码,也不会改变内存中的位置。
- 类的方法、属性以及协议在编译期间存放到了“错误”的位置,直到
realizeClass执行之后,才放到了class_rw_t指向的只读区域class_ro_t,这样我们即可以在运行时为class_rw_t添加方法,也不会影响类的只读结构。 - 在
class_ro_t中的属性在运行期间就不能改变了,再添加方法时,会修改class_rw_t中的methods列表,而不是class_ro_t中的baseMethods,对于方法的添加会在之后的文章中分析。
文章参考:
深入解析 ObjC 中方法的结构