首先我们知道对象创建开辟内存,存放isa指针以及其他成员变量的值。
类的探究
-
LGPerson *p = [LGPerson alloc];
创建一个对象 -
获取对象 p 地址
- p/x
- 得到 (LGPerson *) $2 = 0x000000010058c5f0
-
根据地址获取数据
- x/4gx 0x000000010058c5f0
- 结果:
0x10058c5f0: 0x011d800100008365 0x0000000000000000
0x10058c600: 0x0000000000000000 0x0000000000000000 -
获取isa 0x011d800100008365
-
isa & 掩码 获取对象的类地址
- p/x 0x011d800100008365 & 0x7ffffffffff8
- 得到结果:(long) $3 = 0x0000000100008360
- po 0x0000000100008360 得到 LGPerson
-
获取类地址获取数据
- x/4gx 0x0000000100008360 (类地址)
- 结果:
0x100008360: 0x0000000100008338 0x00007fff808b2008
0x100008370: 0x00007fff204a3aa0 0x0000802c00000000 -
获取到类的isa 0x0000000100008338
-
类isa & 掩码 获取元类地址 并打印
- po 0x0000000100008338 & 0x7ffffffffff8
- 结果 LGPerson
- p/x 0x0000000100008338 & 0x7ffffffffff8
- (long) $6 = 0x0000000100008338 发现还是 8338
-
思考 0x0000000100008360 地址 和 0x0000000100008338 地址 都是打印的LGPerson
-
猜想:类和对象一样也会开辟开辟内存,内存中不只有一个类
验证
//MARK: - 分析类对象内存存在个数
void lgTestClassNum(void){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
- 结果
0x100008360-
0x100008360-
0x100008360-
0x100008360
- 说明真正的类地址是 100008360 不是 100008338 类地址只有一个,
- 100008338则是元类地址
复制代码
可执行文件分析
- 利用MachOView 打开可执行文件
- 打开 __objc_classrefs 发现并没有mataclass的信息 只有对象的信息
- Symbol Tabel 中查找 MetaClass – 发现了一个
_objc_metaclass_$_LGPerson
$ 链接符号 无实义
isa 指向分析
-
元类由系统编译完成创建由类的isa&掩码指向
- 对象instance.isa & 掩码 -> 类.isa & 掩码 -> 元类.isa & 掩码 -> 根元类
-
打印元类地址 (lldb) x/4gx 0x0000000100008338
- 结果
0x100008338: 0x00007fff808b1fe0 0x00007fff808b1fe0
0x100008348: 0x000000010063f080 0x0001e03500000007
- 结果
-
元类ISA & 掩码
- (lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
- 结果:(long) $7 = 0x00007fff808b1fe0
-
po 打印
- (lldb) po 0x00007fff808b1fe0
- 结果:NSObject
-
元类的isa
-
NSObject 元类探究
-
1.获取NSObject 类地址
- (lldb) p/x NSObject.class
- 结果:(Class) $9 = 0x00007fff808b2008 NSObject
-
2.获取数据
- (lldb) x/4gx 0x00007fff808b2008
- 结果:
0x7fff808b2008: 0x00007fff808b1fe0 0x0000000000000000
0x7fff808b2018: 0x0000000100586990 0x0002801000000003 -
3.NSObject类.ISA & 掩码 获取 NSObject元类
- (lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
- 结果:(long) $10 = 0x00007fff808b1fe0
-
-
根类.isa & 掩码 -> 根元类
-
结论图:这里我们先放下继承的关系先仅仅从isa的指向得出结论
- 对象instance.isa & 掩码 -> 类.isa & 掩码 -> 元类.isa & 掩码 -> 根元类
-
代码验证
###### 代码打印
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
- 结果
0x100705b50 实例对象
0x7fff808b2008 类
0x7fff808b1fe0 元类
0x7fff808b1fe0 根元类
0x7fff808b1fe0 根根元类
复制代码
- 刚好验证了这幅图的结果
继承 superclass 对isa指向的影响分析
- 还是代码查看
#pragma mark - NSObject 元类链
void lgTestNSObject(void){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
// LGPerson元类
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
}
复制代码
- 结果 这里我补充了几个地址
0x100504990 实例对象
0x7fff808b2008 类
0x7fff808b1fe0 元类
0x7fff808b1fe0 根元类
0x7fff808b1fe0 根根元类
2021-06-19 23:06:45.555472+0800 002-isa分析[1518:109172] NSObject - 0x7fff808b1fe0
2021-06-19 23:06:45.555585+0800 002-isa分析[1518:109172] LGPerson - 0x100008338
(lldb) p LGPerson.class
(Class) $0 = LGPerson
(lldb) p LGPerson.class
(Class) $1 = LGPerson
(lldb) p/x LGPerson.class
(Class) $2 = 0x0000000100008360 LGPerson
2021-06-19 23:12:10.220883+0800 002-isa分析[1518:109172] (null) - 0x0
(lldb) p pMetaClass
(Class) $3 = 0x0000000100008338
(lldb) p LGTeacher.class
(Class) $4 = LGTeacher
(lldb) p/x LGTeacher.class
(Class) $5 = 0x0000000100008310 LGTeacher
(lldb) p tMetaClass
(Class) $6 = 0x00000001000082e8
(lldb) p/x 0x00000001000082e8
(long) $7 = 0x00000001000082e8
(lldb) x/4gx 0x00000001000082e8
0x1000082e8: 0x00007fff808b1fe0 0x0000000100008338
0x1000082f8: 0x00000001038223c0 0x0002e03500000003
(lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
(long) $8 = 0x00007fff808b1fe0
(lldb)
复制代码
- 直观看图
- 注意:class_getSuperclass 这个函数不要等同于 通过isa & 掩码 这个只是查找父类 所以
Class tsuperClass = class_getSuperclass(tMetaClass);
找到的是8338
(lldb) p tMetaClass
(Class) $6 = 0x00000001000082e8
(lldb) p/x 0x00000001000082e8
(long) $7 = 0x00000001000082e8
(lldb) x/4gx 0x00000001000082e8
0x1000082e8: 0x00007fff808b1fe0 0x0000000100008338
0x1000082f8: 0x00000001038223c0 0x0002e03500000003
(lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
(long) $8 = 0x00007fff808b1fe0
以上才是正确查找 通过isa 找到 刚好验证了 (subclass(meta))->(root class(meta))
复制代码
- 继承的关系 元类也是有继承关系的
- 还是通过上面的代码研究但是要补充一下
- LLDB 继续调试
(lldb) p/x class_getSuperclass(LGPerson.class)
(Class _Nullable) $1 = 0x00007fff808b2008 NSObject
(lldb) p/x class_getSuperclass(tMetaClass)
(Class _Nullable) $2 = 0x0000000100008338
(lldb) p/x class_getSuperclass(LGTeacher.class)
(Class _Nullable) $3 = 0x0000000100008360 LGPerson
(lldb) p/x class_getSuperclass(tMetaClass)
(Class _Nullable) $4 = 0x0000000100008338
(lldb) p/x class_getSuperclass(NSObject.class)
(Class _Nullable) $5 = nil
(lldb) p/x class_getSuperclass(metaClass)
(Class _Nullable) $6 = 0x00007fff808b2008 NSObject
(lldb)
复制代码
- 直观看图
- 整合起来就是这张图了,这张图包含了继承关系和isa的指向关系,class关系和superclass关系,感觉还是分开记比较好,当面试问你的时候想理解问的是isa关系还是继承关系,然后选择不一样的图进行回答,不然看这个图感觉会记晕了。
- 这个就是这两条线
- 所以得出结论对象是没有继承关系的只有类、元类才有继承关系,我们不能说 Person * p , Teacher * t,t 继承 p 。
objc_class 数据结构的探索
- 查看 objc-runtime-new
#if __has_feature(ptrauth_calls)
- __has_feature : 此函数功能是判断编译器是否支持某个功能
- ptrauth_calls :指针身份验证,针对arm64e架构:使用apple A12 或者更高版本A系列处理器设备(如:XS、XS max 、XR 或更新设备)支持arm64e
- objc_class 继承自 objc_object 结构体 objc_object结构体中有 isa,所以objc_class有个隐藏属性isa
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
复制代码
- 所以这个图就比较好理解了,isa是一个联合体,利用位域存放了这个类的各种信息。
-
superclass 当前类的父类
-
catche : 缓存(目前不讨论)
-
现在我们研究的时 class_data_bits_t bits
-
内存空间是一段连续的内存空间,即我们想要获取对象的第一个属性的值得时候只需要将地址+8字节就可以获取第一个属性的值或者地址。同理我们想要获取bits 数组只需要将objc_class的地址+isa+superclass+cache就可以拿到bits的地址。
-
由于cache_t 数据结构超级长,不好查看知道他的长度大小,但是思考,方法不在内存中,static的数据也不是放在内存中,在全局区。
- 只需要查看这部分的内存就可以了。
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
复制代码
- explicit_atomic<uintptr_t> _bucketsAndMaybeMask; 泛型数据
- uintptr_t typedef unsigned long uintptr_t; 无符号Long 8 字节大小
- 联合体只需要查看最大长度即可,根据取其子数据最大大小,
union {
struct {
explicit_atomic<mask_t> _maybeMask; 4字节
#if __LP64__
uint16_t _flags; 2字节
#endif
uint16_t _occupied; 2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; 指针8字节
};
复制代码
- typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
- uint32 4字节
- uint16_t 2字节
- cache_t 为16 字节
- LLDB 调试 : (lldb) po sizeof(cache_t) 16
- isa+superclass+cache = 8+8+16 = 32 = 0x20 就可以拿到bits的地址。
查找属性流程 LLDB
(lldb) p/x LGPerson.class
(Class) $5 = 0x0000000100008380 LGPerson
(lldb) p/x 0x0000000100008380+0x20
(long) $6 = 0x00000001000083a0
(lldb) p/x (class_data_bits_t *)0x00000001000083a0 //转换类型重新打印
(class_data_bits_t *) $7 = 0x00000001000083a0
-------- 找到 class-rw-t
(lldb) x/4gx LGTeacher.class
0x100008330: 0x0000000100008358 0x0000000100008380
0x100008340: 0x00000001003623a0 0x0000802800000000
(lldb) p/x (class_data_bits_t *)0x100008350
(class_data_bits_t *) $14 = 0x0000000100008350
(lldb) p $14->data()
(class_rw_t *) $15 = 0x00000001013111f0
(lldb) p *$15
(class_rw_t) $16 = {
flags = 2282225664
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4314921008
}
}
firstSubclass = nil
nextSiblingClass = nil
}
(lldb) p $14->data()->ro()->instanceSize
(const uint32_t) $18 = 32 这个就是对象未对齐时需要的内存大小
(lldb) p $16.properties()
(const property_array_t) $19 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000080b8
}
arrayAndFlag = 4295000248
}
}
}
-- 获取二维数组list
(lldb) p $19.list
(const RawPtr<property_list_t>) $20 = {
ptr = 0x00000001000080b8
}
(lldb) p $20.ptr
(property_list_t *const) $21 = 0x00000001000080b8
(lldb) p *$21 //请不要在意 21变到了26 因为我上面调试出了点问题
(property_list_t) $26 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $26.get(0)
(property_t) $27 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb)
------- 在练习一次-
(lldb) x/6gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x00000001012b64f0 0x0002802800000003
0x1000083a0: 0x00000001012b6494 0x000000010036a0f0
(lldb) p 0x1000083a0
(long) $1 = 4295000992
(lldb) p (class_data_bits_t *) 0x1000083a0
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001012b6490
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008260
}
arrayAndFlag = 4295000672
}
}
}
Fix-it applied, fixed expression was:
$3->properties()
(lldb) p $4.list
(const RawPtr<property_list_t>) $5 = {
ptr = 0x0000000100008260
}
(lldb) p $5.ptr
(property_list_t *const) $6 = 0x0000000100008260
(lldb) p *$6
(property_list_t) $7 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $7.get(1)
(property_t) $8 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb)
复制代码
查找方法流程
(lldb) x/6gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x000000010131a510 0x0002802800000003
0x1000083a0: 0x000000010131a4d4 0x000000010036a0f0
(lldb) p (class_data_bits_t *) 0x1000083a0
(class_data_bits_t *) $1 = 0x00000001000083a0
(lldb) p $2->methods()
error: <user expression 2>:1:1: use of undeclared identifier '$2'
$2->methods()
^
(lldb) p $1.data()
(class_rw_t *) $2 = 0x000000010131a4d0
Fix-it applied, fixed expression was:
$1->data()
(lldb) p $2->methods()
(const method_array_t) $3 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008160
}
arrayAndFlag = 4295000416
}
}
}
(lldb) p$3.list
error: unknown command shorthand suffix: '$3.list'
(lldb) p $3.list
(const method_list_t_authed_ptr<method_list_t>) $4 = {
ptr = 0x0000000100008160
}
(lldb) p $4.ptr
(method_list_t *const) $5 = 0x0000000100008160
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $6.get(0).big()
(method_t::big) $7 = {
name = "hobby"
types = 0x0000000100003f53 "@16@0:8"
imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb)
复制代码
成员变量的查找
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008368 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100008368
(class_data_bits_t *) $1 = 0x0000000100008368
(lldb) p (class_data_bits_t *)0x0000000100008388
(class_data_bits_t *) $2 = 0x0000000100008388
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101107400
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000320
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $4.ro()
(const class_ro_t *) $5 = 0x0000000100008100
(lldb) p *$5
(const class_ro_t) $6 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003ec8 "LGPerson"
}
}
baseMethodList = 0x0000000100008148
baseProtocols = nil
ivars = 0x00000001000081e0
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008248
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x00000001000081e0
(lldb) p *$7
(const ivar_list_t) $8 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $8.get(0)
(ivar_t) $9 = {
offset = 0x0000000100008300
name = 0x0000000100003f04 "hobby"
type = 0x0000000100003f4c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $8.get(1)
(ivar_t) $10 = {
offset = 0x0000000100008308
name = 0x0000000100003f0a "_age"
type = 0x0000000100003f58 "i"
alignment_raw = 2
size = 4
}
(lldb) p $8.get(2)
(ivar_t) $11 = {
offset = 0x0000000100008310
name = 0x0000000100003f0f "_name"
type = 0x0000000100003f4c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb)
复制代码
- 这个是创建空间时的-请查看以往的文章
//取出数据
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
复制代码
-
结论:
- 成员变量在 类.bits.data().properties() 的 properties_list_t 数组中
- 方法在 类.bits.data().methods() 的 method_array_t 数组中
- 成员变量在 类.bits.data().ro().ivars 的 ivar_list_t 数组中 。
-
调用ro 方法
- 我们可以调用方法获取数据
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};
}
}
-----------------
list_array_tt : 二维数组,数组中的元素还是数组。
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) { }
};
-------
class list_array_tt {
....
//迭代器方法。遍历查找
class iterator{
....
}
....
}
复制代码
- 查找属性
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK); //属性是放在 class_rw_t 中 的 properties
}
复制代码
- 查找方法
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()};
}
}
复制代码
- 属性可以直接查找property_t 就可以找到 name和描述
- 方法可以查找method_t中的big 调用big()方法 就可以找到 name和描述
- 这个是 hobby的get方法
(method_t::big) $7 = {
name = "hobby" //方法名
types = 0x0000000100003f53 "@16@0:8" //方法类型
imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson hobby]) //方法的地址
}
复制代码
- 图形说明位置
类方法的探究
- 在MachOView中其实是可以找到的。
类方法查找流程
(lldb) x/4gx LGPerson.class
0x100008368: 0x0000000100008390 0x000000010036a140
0x100008378: 0x0000000101814150 0x0002802800000003
(lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x0000000100008390
(lldb) p/x (class_data_bits_t *) 0x00000001000083b0
(class_data_bits_t *) $5 = 0x00000001000083b0
(lldb) p $5->data()
(class_rw_t *) $6 = 0x00000001018133b0
(lldb) p *$6
(class_rw_t) $7 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000688
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff80355eb0
}
(lldb) p $7.methods()
(const method_array_t) $8 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000082b8
}
arrayAndFlag = 4295000760
}
}
}
(lldb) p $8.list
(const method_list_t_authed_ptr<method_list_t>) $9 = {
ptr = 0x00000001000082b8
}
(lldb) p $9.ptr
(method_list_t *const) $10 = 0x00000001000082b8
(lldb) p *$10
(method_list_t) $11 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $11.get(0).big()
(method_t::big) $12 = {
name = "say666"
types = 0x0000000100003f44 "v16@0:8"
imp = 0x0000000100003e30 (KCObjcBuild`+[LGPerson say666])
}
(lldb)
复制代码