isa_的指向
上一篇我们了解了isa_t的结构, isa是类之中的一个成员. 现在我们继续往下搞一下. 先看下面两幅图我使用的是x86的mask:
0x0000000100004348 LGPerson -> 0x0000000100004320 LGPerson-> 0x0000000100353190 NSObject
0x00000001003531e0 NSObject -> 0x0000000100353190 NSObject
两个不同的实例, 最终isa都指向了同一个地址的NSObject的地址, 而且NSObject继续mask之后都是本身的地址.
很奇怪, 一个实例对象的isa指向了地址不同的自己又指向了一个NSObject的东西, 一个NSObject的实例对象也是指向了两个不同地址的自己. 但是最终都指向了一个地方, 这到底是什么鬼东西???
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
log:
0x100004380
0x100004380
0x100004380
0x100004380
我们发现不管是实例的class, 还是类的class都是指向同一个地址, 有两个LGPerson我们也不清楚. 我们去macho的符号表, 然后就看到了下图:
一个_OBJC_METACLASS$_LGPerson的东西, 一个MetaClass的东西, 我们自己创建的都是Class, 并没有创建MetaClass, 所以只能是系统或者编译器在我们创建Class的时候帮我们生成的一个类别:
上面的整个过程其实可以猜想出:
普通对象:
实例isa -> 类 isa ->元类isa -> NSObject isa(自己指向自己)
NSObject对象:
实例isa -> 类 isa ->元类isa(自己指向自己)
最终的目的地都是同一地址
接下来就是官方文档的一副流程图:
superclass的走向
由上图可知, superclass的继承链, 我们可以用代码验证一下:
void testSuperClass() {
LGTeacher *t = [LGTeacher alloc];
LGPerson *p = [LGPerson alloc];
NSLog(@"%@-%@",t,p);
NSLog(@"%@",class_getSuperclass(LGTeacher.class));
NSLog(@"%@",class_getSuperclass(LGPerson.class));
NSLog(@"%@",class_getSuperclass(NSObject.class));
}
2021-06-20 09:09:08.740327+0800 ObjcBuild[78576:3928152] <LGTeacher: 0x101d047e0>-<LGPerson: 0x101d04770>
2021-06-20 09:09:08.740447+0800 ObjcBuild[78576:3928152] LGPerson
2021-06-20 09:09:08.740583+0800 ObjcBuild[78576:3928152] NSObject
2021-06-20 09:09:08.740719+0800 ObjcBuild[78576:3928152] (null)
上述代码我们可以得知类对象的继承关系, 最终NSObject类是没有父类的.
接下来我们继续验证一下元类的superClass走向:
先确认继承关系 LGTeacher -> LGPerson -> NSObject
NSObject *obj = [NSObject alloc];
Class class1 = object_getClass(obj);
Class classMeta = object_getClass(class1);
Class classRootMeta = object_getClass(classMeta);
Class classRootRootMeta = object_getClass(classRootMeta);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",obj,class1,classMeta,classRootMeta,classRootRootMeta);
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"LGTeacher的父类->%@ - %p",tsuperClass,tsuperClass);
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"LGPerson的父类->%@ - %p",psuperClass,psuperClass);
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"NSObject的父类%@ - %p",nsuperClass,nsuperClass);
Class rnsuperClass = class_getSuperclass(classMeta);
NSLog(@"NSObject根源类的父类%@ - %p",rnsuperClass,rnsuperClass);
0x101042830 实例对象
0x1003571e0 类
0x100357190 元类
0x100357190 根元类
0x100357190 根根元类
LGTeacher的父类->LGPerson - 0x100004448
LGPerson的父类->NSObject - 0x100357190
NSObject的父类(null) - 0x0
NSObject根源类的父类NSObject - 0x1003571e0
整个demo的验证, 最大的亮点还是最后的根源类的地址, 和isa的指向不一样的, 根源类最后的superClass是指向了NSObject类的地址;
和isa流程图中的superClass的指向一致.
类的本质objc_class
之前的文章中我们写过: Class, id是什么
Class 就是 objc_class 结构体指针, 定义为objc_class *
id 就是 objc_object 结构体指针objc_object *
我们看一下objc_class在源码中对应的实际结构:
上图所示, 这显然不是我要的, 然后我根据指引注释搜索了*Class如下图:
但是我在点击objc_class的时候却发现点击不进去, 然后我直接搜索了定义
在objc-runtime-new中找到了, objc_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 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_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...
}
我把比较重要的部分先挑出来, 之前我们说isa_t里面的位域的哪些位置代表了什么, 之前的对齐算法 , 在objc_class里面都有提供对应的方法. 用到什么看什么, 这里不多提了. 继续往下看:
我把上述代码每个结构都点击进去看了一遍, 在class_rw_t结构中发现了methods() 的方法, 还有一个就是调用data()方法实际是bits.data(); 代如下
struct class_data_bits_t {
...省略
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
...省略
}
struct class_rw_t {
...省略
Class firstSubclass;
Class nextSiblingClass;
...省略
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};
}
}
//在此我们就找到了属性, 方法等列表
}
nextSiblingClass下一个兄弟类, 只要看到Sibling这个单词其实就可以想象常用的数据结构到只有红黑树用到了兄弟节点, 我们可以猜测其实类的底层是以树状结构联系在一起的.
接下来我们怎么查看, 找到这个methodList呢?
在结构体中, 我们想要访问对应的数据, 就是拿到结构体指针然后根据偏移量找到对应的成员. 所以我们只要知道ISA(父类的), superclass和cache的大小, 然后就可以找到bits的地址.
struct objc_class : objc_object {
...
// Class ISA; 父类的成员, Class就是objc_class *, 是一个指针, 8字节
Class superclass; //和上面一样8字节
cache_t cache; // 未知
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
上述代码可知, 我们只需要找到cache_t的大小 , 就可以根据首地址计算出bits的偏移量, 从而找到bits的结构, cache_t如下:
struct cache_t {
private:
//uintptr_t typedef unsigned long
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask; //uint32_t
#if __LP64__
uint16_t _flags; //uint16_t
#endif
uint16_t _occupied; //uint16_t
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
...
其余的部分, 全都是方法和staic静态变量, 还有const标注的常量, 内存并不在结构体之中, 最终简化的代码如上述
上述代码可知是一个cache_t是一个结构体, 内部的成员是一个
- explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
- 首先explicit_atomic点击进入发现是一个泛型设计, 根据传入的数据进行返回的, 所以看uintptr_t就行, uintptr_t是typedef unsigned long无符号长整型8字节 ;
- union联合体
- 联合体内部也一样, 直接看preopt_cache_t *这个就是preopt_cache_t的结构体指针8字节, 也可以直接查联合体中的mask_t(4), _flags(2),_occupied(2)也是8字节.
所以上述原因可知, cache_t是一个16字节大小的结构体.
所以可以得到bits的偏移量为8+8+16 = 32 = 0x20;
下面用看一下调试过程:
我们根据上述可以找到结构体中对应的methods, properties协议等, 但是继续往下却不知道怎么找了. 我们看上述代码可以知道property_array_t的一个结构体, 点击进去看一下, list的实例成员:
class method_array_t :
public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
///省略
};
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) { }
};
继续看一下list_array_tt结构体
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize() {
return byteSize(count);
}
};
//////省略
public:
发现了一个list, 我们可以输出一下试试. 打印出来是property_list_t数组, 把数组的ptr取出.
union {
Ptr<List> list;
uintptr_t arrayAndFlag;
};
//////省略
}
然后根据list取出数组首地址, 找到数组.
但是找到了数组, 试了很多方法只能打印出数组的首地址, 数据并不知道怎么打印出来:
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
property_list_t是空实现, 我们全局搜索继承的entsize_list_tt结构
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
省略大部分代码
Element& getOrEnd(uint32_t i) const {
ASSERT(i <= count);
return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
//在这里可以找到一个getOrEnd和get方法, 传入下标返回Element元素.
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
size_t byteSize() const {
return byteSize(entsize(), count);
}
static size_t byteSize(uint32_t entsize, uint32_t count) {
return sizeof(entsize_list_tt) + count*entsize;
}
};
所以最终就查找到了元素的存储, 并且打印出来
然后我继续查看了一下method的方法:
可以打印出list, 但是缺看不到方法名??? 于是我又找到了method_t,
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
.....
}
所以直接把big输出出来就好了, 下图
总结
遗留了两个问题
- 自己定义的成员变量并没有在属性列表中
- 自己定义的类方法, 并没有在类的方法列表中
篇幅太长, 下篇继续.