isa指针指向分析
类对象
我们在objc源码中查找Class的实现代码如下(因内容太多省略大部分代码)。
typedef struct objc_class *Class;
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 superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
从源码中可以看出,Class是一个叫做objc_class的结构体,而这个结构体又继承自objc_object结构体,在对对象的探索过程中发现了对象实际上就是objc_object结构体,所以类的本质也是对象,也是objc_object结构体。
内存中有几个类对象
我们对同一个类的使用多个对象来获取类对象,然后查看获取到的类对象,看看有什么发现。
void lgTestClassNum(void){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p",class1,class2,class3);
// 打印结果为:0x100008368-0x100008368-0x100008368
}
通过打印结果发现,就算创建了多个对象,所有对象的类对象指向了同一个内存地址,所以对象的类对象在内存中仅存在一个。
类对象的isa指针
由于类对象是继承自objc_object,那它就继承了isa指针。对象的isa指针指向类对象,那类对象的isa又指向何方呢?
我们通过运行代码, 打断点,查看内存地址来一步步探索其中的奥秘。首先我们先查看对象p的4个8字节的内存地址。
接下来探索过程描述和图片结合如下。
探索到这里,isa指针的指向已经到了头。虽然对一些isa指针所指向的对象我们并不清楚,但基本的isa指向方法我们有个大概的了解了。
接下来,我们通过runtime来帮助我们认识这些未知的东西。
最终我们将那么未知的对象找了出来,有了如下结论。
完整的isa指针指向图如下
元类的继承链
定义LGPerson类继承自NSObject,定义LGTeacher类继承自LGPerson。
@interface LGPerson : NSObject
@end
@interface LGTeacher : LGPerson
@end
接下来,通过runtime来探索元类的继承关系。
void testMetaSuper(void){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类对象
Class class = object_getClass(object1);
// NSObject元类(根元类),获取类对象的类等同于获取元类,即objc_getMetaClass("NSObject");
Class metaClass = object_getClass(class);
// 打印为:NSObject实例对象:0x105326780
NSLog(@"NSObject实例对象:%p",object1);
// 打印为:NSObject类对象:0x7ff84a759030
NSLog(@"NSObject类对象:%p",class);
// 打印为:NSObject元类(根元类):0x7ff84a758fe0
NSLog(@"NSObject元类(根元类):%p",metaClass);
// LGPerson -- 元类的父类就是父类的元类
Class pMetaClass = objc_getMetaClass("LGPerson");
Class psuperClass = class_getSuperclass(pMetaClass);
// 打印为:LGPerson - 0x100008340
NSLog(@"%@ - %p",pMetaClass,pMetaClass);
// 打印为:NSObject - 0x7ff84a758fe0,LGPerson元类的父类是根元类
NSLog(@"%@ - %p",psuperClass,psuperClass);
// LGTeacher元类的父类 就是 LGPerson(LGPerson的元类)
Class tMetaClass = objc_getMetaClass("LGTeacher");
Class tsuperClass = class_getSuperclass(tMetaClass);
// 打印为:LGPerson - 0x100008340,LGTeacher元类的父类是LGPerson的元类
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
// NSObject的父类
Class nsuperClass = class_getSuperclass(NSObject.class);
// 打印为:(null) - 0x0,NSObject类对象没有父类
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
// 根元类的父类 -- NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
// 打印为:NSObject - 0x7ff84a759030,根元类的父类是NSObject类对象
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
}
由上面的代码,我们可以得出元类的继承关系图。
内存平移
// 数组指针
int c[4] = {5,6,7,9};
int *d= c;
NSLog(@"%p - %p - %p - %p - %p",&c,&c[0],&c[1],&c[2],&c[3]);
NSLog(@"%p - %p - %p - %p",d,d+1,d+2,d+3);
for (int i = 0; i<4; i++) {
int value = *(d+i);
NSLog(@"%d",value);
}
// 打印如下
// 0x7ff7bfeff2c0 - 0x7ff7bfeff2c0 - 0x7ff7bfeff2c4 - 0x7ff7bfeff2c8 - 0x7ff7bfeff2cc
// 0x7ff7bfeff2c0 - 0x7ff7bfeff2c4 - 0x7ff7bfeff2c8 - 0x7ff7bfeff2cc
// 5
// 6
// 7
// 9
d是数组c的首地址指针,当对d进行+操作时,就是对c数组以数组类型为大小进行内存平移,即d+1=c[1],以此类推。
实例方法、属性在类中的存储位置
实例方法
查看源码分析
虽然我们查看了objc_class源码的实现,但是并没有在其中发现类的方法或属性之类的信息,于是我们继续查看一些成员变量的实现。
objc_class中有一个class_data_bits_t类型的成员,那class_data_bits_t又是什么呢?我们接着查看它的源码。
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
// 省略了一些非关键代码
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 省略了一些非关键代码
}
class_data_bits_t中有一个指针bits,另外还有一个data函数,返回class_rw_t类型的指针,查看class_rw_t源码(省略了部分代码)。
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
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);
}
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};
}
}
};
我们惊奇地发现,在class_rw_t中存在着命名为methods,properties,protocols这些返回了数组的函数,是否这些函数返回的就是类所拥有的方法、属性和遵从的协议呢?
验证
既然我们已经通过源码找到了探索的方向,接下来我们通过代码和控制台来帮我们验证一下。
先定义一个类用于我们做验证。
@interface LGPerson : NSObject {
NSString *_hobby;
}
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
首先我们需要获取到class_data_bits_t。但我们应该怎样得到class_data_bits_t呢?
我们继续查看最开始我们贴出的objc_class的实现,由于其继承了objc_objct,所以它也应该有一个8字节的isa指针,Class superclass是8字节的指针,cache_t cache占16字节(后续再具体探索cache_t,当前只要知道它占16字节就行),class_data_bits_t bits占多少字节暂不得而知。
那我们得出结论,class_data_bits_t是在类的首地址偏移32字节的位置。知道了位置以后,我们就可以通过类对象的首地址来进行偏移得到class_data_bits_t。下图中类对象的首地址为0x100008250,偏移32位即0x100008270。由于我们已经知道了这段内存地址存储的是什么东西,那我们就可以直接对其进行强转。
当我们已经有了
class_data_bits_t的指针以后,就可以对其进行一系列的操作,一步步验证我们刚才根据源码获取到的信息。
我们查看
method_list_t的实现,发现它是entsize_list_tt模板类的派生类,那我们接着查看entsize_list_tt。
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
}
entsize_list_tt是一个容器模板,并且我们在它里面发现了一个可以获取其元素的Element& get(uint32_t i)函数。
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
uint32_t flags() const {
return entsizeAndFlags & FlagMask;
}
Element& getOrEnd(uint32_t i) const {
ASSERT(i <= count);
return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
// 省略后面的代码
}
我们尝试通过get函数来获取刚才已经得到的method list。
经过尝试,我们并没有获取到有用的信息,于是我们继续查看
method_t的源码。
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
struct small {
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP, /*isNullable*/false> imp;
bool inSharedCache() const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this));
}
};
objc_method_description *getDescription() const {
return isSmall() ? getSmallDescription() : (struct objc_method_description *)this;
}
};
struct objc_method_description {
SEL _Nullable name; /**< The name of the method */
char * _Nullable types; /**< The types of the method arguments */
};
我们发现在method_t中有big和small两个结构体,其中都有包含name,type等信息,并且有一个getDescription函数返回一个objc_method_description指针,而objc_method_description结构体里,也定义了SEL name和char * types,那我们就尝试通过getDescription来获取我们想要的信息。
果不其然,找到了我们想要的东西,我们定义的
instanceMethod方法。那接下来把list中所有的都取出来看一下。
methods中包括两个属性的get和set方法,还有我们定义的实例方法
instanceMethod,还有析构方法,但是并没有发现我们定义的类方法,那类方法又是存放在哪里呢?先暂时放下这个疑问,我们用同样的方法查看一下属性。
属性
在class_rw_t中,properties返回的是property_array_t,这个跟methods一样,其中存放的是property_t结构体。
class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
struct property_t {
const char *name;
const char *attributes;
};
已经知道了properties的结构,那我们用同样的方法来获取properties。
同样的,我们只看到了
name和age属性,成员变量hobby并没有在properties中。
类方法和成员变量究竟存储在哪里呢?下一篇接着探索。