isa的分析
以LGPerson为例来进行分析
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
NSLog(@"--->%@",p);
}
复制代码
LLDB调试如下:
//查看对象的值
(lldb) po p
<LGPerson: 0x107c0bcc0>
//打印对象的内存,一般第一段存储的就是isa的地址
(lldb) x/4gx p
0x107c0bcc0: 0x011d8001000082c5 0x0000000000000000
0x107c0bcd0: 0x0000000000000000 0x0000000000000000
//将对象的isa地址与掩码进行 ‘&’操作
//0x00007ffffffffff8UL 这个掩码的值是固定的
(lldb) p/x 0x011d8001000082c5 & 0x00007ffffffffff8UL
(unsigned long) $2 = 0x00000001000082c0
//得到的是 LGPerson
(lldb) po 0x00000001000082c0
LGPerson
复制代码
接下来继续分析LGPerson
//查看LGPerson的内存情况
(lldb) x/4gx 0x00000001000082c0
0x1000082c0: 0x0000000100008298 0x00007fff88b86cc8
0x1000082d0: 0x000000010052b7a0 0x0002802400000003
//将LGPerson的isa与掩码进行'&'操作
(lldb) p/x 0x0000000100008298 & 0x00007ffffffffff8UL
(unsigned long) $4 = 0x0000000100008298
//打印该地址存放的值
(lldb) po 0x0000000100008298
LGPerson
复制代码
上一步得到一个LGPerson,这一步又得到一个LGPerson,名字相同,但是内存地址不一样 0x00000001000082c0 != 0x0000000100008298
,这是什么情况呢?LGPerson
是类名,在内存中有多个这样的类存在吗?类像对象一样可以在内存中创建多次吗?
类的深层探索
在上面的基础上,通过多种方式创建多个类,查看他们的内存地址
Class cls1 = [LGPerson class];
Class cls2 = [[LGPerson alloc] init].class;
Class cls3 = object_getClass([LGPerson alloc]);
NSLog(@"cls1 ==== %p", cls1);
NSLog(@"cls2 ==== %p", cls2);
NSLog(@"cls3 ==== %p", cls3);
复制代码
打印结果:
2021-06-26 10:44:29.889553+0800 [1452:53722] cls1 ==== 0x1000082d8
2021-06-26 10:44:29.889632+0800 [1452:53722] cls2 ==== 0x1000082d8
2021-06-26 10:44:31.411735+0800 [1452:53722] cls3 ==== 0x1000082d8
复制代码
可以看出来,这三种不同的创建类的方式,得出的类的内存地址都是一样的,说明类在内存中是唯一的。那么第一次探索的时候,为何会有两个名字一样的LGPerson
呢,那个地址不一样的LGPerson
到底是什么呢?接下来继续探索吧。
将可执行文件放到MachOView查看,找到符号表,在根据地址找到对应的符号所在的位置:0x00000001000082d8
和0x00000001000082b0
可以看出来一开始得到的两个类里面的第一个类是我们的LGPerson
类 _OBJC_CLASS_$_LGPerson
,第二个类LGPerson
是_OBJC_METACLASS_$_LGPerson
我们称他为LGPerson
的元类,是由系统生成的。
isa的走位
基于上的分析,进一步探索isa的指向
(lldb) x/4gx 0x00000001000082b0
0x1000082b0: 0x00007fff88b86ca0 0x00007fff88b86ca0
0x1000082c0: 0x00000001078c71a0 0x0002e03500000003
(lldb) p/x 0x00007fff88b86ca0 & 0x00007ffffffffff8UL
(unsigned long) $4 = 0x00007fff88b86ca0
(lldb) po $4
NSObject
(lldb) x/4gx 0x00007fff88b86ca0
0x7fff88b86ca0: 0x00007fff88b86ca0 0x00007fff88b86cc8
0x7fff88b86cb0: 0x00000001078c74c0 0x0003e03100000007
复制代码
从上面的结果可以看到,元类的isa
指向NSObject
,NSObject
的isa
指向它本身,我们称NSObject
为根元类。
通过以上的探索,可以验证isa的流程图:
元类的继承链
先补充一个知识点:
object_getClass
该方法返回的是自身的isa指向的地址。
通过实例来分析元类的继承链
LGPerson *object = [LGPerson alloc];
Class class1 = object_getClass(object);
Class metaClass = object_getClass(class1);
Class rootMetaClass = object_getClass(metaClass);
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"实例对象:%p",object);
NSLog(@"类: %p",class1);
NSLog(@"元类:%p",metaClass);
NSLog(@"元类的父类:%p",class_getSuperclass(metaClass));
NSLog(@"根元类:%p",rootMetaClass);
NSLog(@"根元类的父类:%p",class_getSuperclass(rootMetaClass));
NSLog(@"根元类的根元类:%p",rootRootMetaClass);
复制代码
2021-06-26 16:36:11.113527+0800[2512:198583] 实例对象:0x1074040c0
2021-06-26 16:36:11.113853+0800[2512:198583] 类: 0x1000082d0
2021-06-26 16:36:11.113889+0800[2512:198583] 元类:0x1000082a8
2021-06-26 16:36:11.113930+0800[2512:198583] 元类的父类:0x7fff88b86ca0
2021-06-26 16:36:11.113963+0800[2512:198583] 根元类:0x7fff88b86ca0
2021-06-26 16:36:11.113989+0800[2512:198583] 根元类的父类:0x7fff88b86cc8
2021-06-26 16:36:24.996991+0800[2512:198583] 根元类的根元类:0x7fff88b86ca0
复制代码
从上面可以看出,元类的父类
和 根原类
是一样的,也就是说 对象的元类的父类就是当前对象的根元类
。
类的结构分析
类在底层是一个objc_class
的结构体
在objc/runtime.h
中objc_class
结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //指向元类的objc_class结构体指针,iOS中的类也是对象,元类中储存有类对象的类方法;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
复制代码
objc_class
的定义:
struct objc_class : objc_object {
······
// 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 getSuperclass() const {
·····
复制代码
objc_object
的定义:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
复制代码
类的结构内存计算
分析LGPerson的内存情况
(lldb) x/4gx LGPerson.class
0x100008600: 0x0000000100008628 0x000000010036a140
0x100008610: 0x0000000100640fd0 0x000780480000000f
复制代码
Class ISA
,占用8字节,因此0x0000000100008628
存的是isa
;
Class superClass
,占用8字节,因此0x000000010036a140
存的是superClass
;
cache_t
结构体比较复杂,需要分析一下:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 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
};
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// _bucketsAndMaybeMask is a buckets_t pointer
// _maybeMask is the buckets mask
static constexpr uintptr_t bucketsMask = ~0ul;
···
复制代码
分析占用内存大小,影响内存的因素是成员变量,static修饰的变量存在全局区,不影响结构体内存的大小,因此只需要考虑前面几个成员变量占用的内存情况就可以了。
_bucketsAndMaybeMask
是 uintptr_t
类型 ,占用8字节;
union
为联合体,成员变量互斥,内部成员变量占用同一块内存,较大的成员变量占用的内存大小就是联合体的大小
union {
struct {
explicit_atomic<mask_t> _maybeMask; // mask_t为uint32_t 占4字节
#if __LP64__
uint16_t _flags; // uint16_t 占2字节
#endif
uint16_t _occupied; // uint16_t 占2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 占8字节
};
复制代码
因此,union
占用了8个字节 ,cache_t
占用了16个字节。
bits的地址即为: 类的首地址偏移8 + 8 + 16 = 32
字节及偏移 0x20
。
LLDB 分析类的结构
LGPerson
类
@interface LGPerson : NSObject
@property (nonatomic) int age;
@property (nonatomic, strong) NSString *hobby;
- (void)saySomething;
+ (void)doSomething;
@end
#import "LGPerson.h"
@interface LGPerson ()
{
NSString *name;
}
@end
@implementation LGPerson
- (instancetype)init{
if (self = [super init]) {
}
return self;
}
- (void)saySomething{
}
+ (void)doSomething{
}
@end
复制代码
main
函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
}
return 0;
}
复制代码
lldb
调试:
(lldb) x/4gx person
0x1013040a0: 0x011d8001000083e1 0x0000000000000000
0x1013040b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x1013040a0 + 0x20
(long) $1 = 0x00000001013040c0
(lldb) p (class_data_bits_t *)0x00000001013040c0
(class_data_bits_t *) $2 = 0x00000001013040c0
(lldb) p $2
(class_data_bits_t *) $2 = 0x00000001013040c0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00006957534e5b28
(lldb) p *$3
复制代码
上面这种方式打印不出来$3
的值,报错内容是违反了对象的唯一性约束。换下面这种方式打印:
(lldb) x/4gx LGPerson.class
0x1000083e0: 0x0000000100008408 0x000000010036a140
0x1000083f0: 0x0000000100362380 0x0000802800000000
(lldb) p/x 0x1000083e0+0x20
(long) $1 = 0x0000000100008400
(lldb) p (class_data_bits_t *)0x0000000100008400
(class_data_bits_t *) $2 = 0x0000000100008400
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101211190
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000440
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
复制代码
属性获取
(lldb) p $4.properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000082c0
}
arrayAndFlag = 4295000768
}
}
}
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
ptr = 0x00000001000082c0
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x00000001000082c0
(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
//get是C++方法
(lldb) p $8.get(0)
(property_t) $9 = (name = "age", attributes = "Ti,N,V_age")
(lldb) p $8.get(1)
(property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",&,N,V_hobby")
复制代码
通过上面的调试得到了该类的属性列表。
源码的探索过程:
方法获取
(lldb) p $4.methods()
(const method_array_t) $11 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000081c0
}
arrayAndFlag = 4295000512
}
}
}
(lldb) p $11.list
(const method_list_t_authed_ptr<method_list_t>) $12 = {
ptr = 0x00000001000081c0
}
(lldb) p $12.ptr
(method_list_t *const) $13 = 0x00000001000081c0
(lldb) p *$13
(method_list_t) $14 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $14.get(0)
(method_t) $15 = {}
复制代码
用跟属性同样的方法去获取方法列表,结果获取不到,这是为什么呢?推测,他们内部的存储结构可能不一样。
进入methods
的源码去查看:
struct class_rw_t{
...
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()};
}
}
...
}
复制代码
返回的类型为 method_array_t
,进入定义查看:
class method_array_t :
public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
public:
method_array_t() : Super() { }
method_array_t(method_list_t *l) : Super(l) { }
const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
return beginLists();
}
const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
复制代码
method_array_t
继承自 list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
,查看 method_t
和 method_list_t
method_list_t
的定义:
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
bool isUniqued() const;
bool isFixedUp() const;
void setFixedUp();
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
ASSERT(i < count);
return i;
}
bool isSmallList() const {
return flags() & method_t::smallMethodListFlag;
}
bool isExpectedSize() const {
if (isSmallList())
return entsize() == method_t::smallSize;
else
return entsize() == method_t::bigSize;
}
method_list_t *duplicate() const {
method_list_t *dup;
if (isSmallList()) {
dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1);
dup->entsizeAndFlags = method_t::bigSize;
} else {
dup = (method_list_t *)calloc(this->byteSize(), 1);
dup->entsizeAndFlags = this->entsizeAndFlags;
}
dup->count = this->count;
std::copy(begin(), end(), dup->begin());
return dup;
}
};
复制代码
method_t
的定义:
struct method_t {
...
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
...
}
复制代码
这里可以看到method_t
里面的big
和property_t
的结构很详细,推测是不是可以通过big
获取方法,验证如下:
(lldb) p $14.get(0).big()
(method_t::big) $16 = {
name = "hobby"
types = 0x0000000100003f67 "@16@0:8"
imp = 0x0000000100003da0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $14.get(1).big()
(method_t::big) $18 = {
name = "setHobby:"
types = 0x0000000100003f77 "v24@0:8@16"
imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $14.get(2).big()
(method_t::big) $19 = {
name = "saySomething"
types = 0x0000000100003f6f "v16@0:8"
imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson saySomething])
}
(lldb) p $14.get(3).big()
(method_t::big) $20 = {
name = "init"
types = 0x0000000100003f67 "@16@0:8"
imp = 0x0000000100003d00 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $14.get(4).big()
(method_t::big) $21 = {
name = "age"
types = 0x0000000100003f84 "i16@0:8"
imp = 0x0000000100003d60 (KCObjcBuild`-[LGPerson age])
}
(lldb) p $14.get(5).big()
(method_t::big) $22 = {
name = "setAge:"
types = 0x0000000100003f8c "v20@0:8i16"
imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setAge:])
}
复制代码
最终成功获取到了相应的方法。