一、isa->类和元类
上篇文章分析了对象的isa
底层实现以及是如何与cls
关联的,这边文章继续分析类的结构。
HPObject *obj = [HPObject alloc];
对obj
打断点查看:
-
x/4gx
获取obj
的isa
指针。 -
isa & mask
获取类对象HPObject
。 -
po
验证获取到的是HPObject
。
这个时候如果对类对象继续x/4gx
呢?
发现
HPObject
的isa
&mask
后也是HPObject
,但是两者的地址不一样。并且类对象MASK
前后地址没有变化说明类对象的isa
是一个单纯的指针,没有位域信息。
1.1 类对象内存个数
既然上面验证出来类对象也是会开辟空间的,那么类对象在内存中有多少份呢?
验证代码:
void verifyClassNumber() {
Class class1 = [HPObject class];
Class class2 = [HPObject alloc].class;
Class class3 = object_getClass([HPObject alloc]);
Class class4 = objc_getClass("HPObject");
//再次创建对象
Class class5 = [HPObject alloc].class;
NSLog(@"\n%p\n%p\n%p\n%p\n%p",class1,class2,class3,class4,class5);
}
结果:
0x1000082f0
0x1000082f0
0x1000082f0
0x1000082f0
0x1000082f0
可以看到都指向一个内存地址,所以只存在一份。这几种获取类对象的方式区别如下:
上面通过HPObject
的isa
&mask
后也是HPObject
,但是两者的地址不一样。说明类对象的isa
指针获取的不是类对象。那么它是什么?(元类)。
这个时候可以用MachOView
查看下符号表:
看到有一个
METACLASS_HPObject
,但是元类并不是我们创建的。那么意味着是系统生成和编译的。
所以就有对应关系:
实例对象(isa)->类对象(isa)->元类
。
二、isa走位图和继承链
2.1 isa走位图
上面得到了实例对象(isa)->类对象(isa)->元类
,这个时候又有一个疑问,元类的isa
指向哪里呢?
实例对象(isa)->类对象(sia)->元类(isa)->根元类(NSObject isa)->自身
根类实例对象(isa)->根元类(isa)->自身
代码验证下:
void verifyIsaLinked() {
//NSObject 实例对象
NSObject *obj = [NSObject alloc];
//NSObject类
Class class = object_getClass(obj);
//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",obj,class,metaClass,rootMetaClass,rootRootMetaClass);
}
结果:
实例对象:0x10136bf30
类:0x100358140
元类:0x1003580f0
根元类:0x1003580f0
根根元类:0x1003580f0
这样就得到了isa
走位图:
2.2 类和元类的继承链
上面分析到了元类,那么元类有父类么?
HPObject元类的父类
Class hpMetaClass = object_getClass(HPObject.class);
//HPObject元类的父类
Class hpSuperMetaClass = class_getSuperclass(hpMetaClass);
NSLog(@"\nHPObject元类的父类:%@ - %p",hpSuperMetaClass,hpSuperMetaClass);
//NSObject元类
Class objMetaClass = object_getClass(NSObject.class);
NSLog(@"\nNSObject元类:%@ - %p",objMetaClass,objMetaClass);
结果:
HPObject元类的父类:NSObject - 0x1003580f0
NSObject元类:NSObject - 0x1003580f0
可以得出结论:HPObject
元类的父类是NSObject
的元类。
HPObject子类(HPSubobject)元类的父类
新建一个HPObject
的子类HPSubobject
同样获取它的元类的父类:
///HPObject元类
Class hpMetaClass = object_getClass(HPObject.class);
NSLog(@"\nHPObject元类:%@ - %p",hpMetaClass,hpMetaClass);
//HPSubobject元类
Class hpsMetaClass = object_getClass(HPSubobject.class);
//HPSubobject元类的父类
Class hpsSuperMetaClass = class_getSuperclass(hpsMetaClass);
NSLog(@"\nHPSubobject元类的父类:%@ - %p",hpsSuperMetaClass,hpsSuperMetaClass);
结果:
HPObject元类:HPObject - 0x1000083b0
HPSubobject元类的父类:HPObject - 0x1000083b0
所以 元类也有继承链。
NSObject(根元类)的父类
那么NSObject
的元类也就是根元类的父类呢?
//NSObject 实例对象
NSObject *obj = [NSObject alloc];
//NSObject类
Class class = object_getClass(obj);
//NSObject元类
Class metaClass = object_getClass(class);
//NSObject元类的父类
Class superMetaClass = class_getSuperclass(metaClass);
NSLog(@"\n类:%@ - %p \n元类的父类:%@ - %p",class,class,superMetaClass,superMetaClass);
结果:
类:NSObject - 0x100358140
元类的父类:NSObject - 0x100358140
可以看到是根元类的父类是NSObject
,万物基于NSObject
。至此元类的继承链就清晰了。
类的继承关系
已知HPSubobject->HPObject->NSObject
,需要验证NSObject
的父类:
Class objSuperClass = class_getSuperclass(NSObject.class);
NSLog(@"\n%@ - %p",objSuperClass,objSuperClass);
结果:
(null) - 0x0
所以NSObject
不存在父类。
这样就得到了完整的类和元类的继承链:
通过isa
的走位链和类的继承关系就得到了那张苹果官网著名的图:
三、源码分析类结构
3.1类的内存结构
既然类也有isa
,那么类的结构是怎样的呢?类在底层是objc_class
类型,在runtime.h
(OBJC2_UNAVAILABLE
)与objc-runtime-new.h
中都有声明。现在使用的都是objc-runtime-new.h
中的objc_class
。它是一个结构体要研究它的结构需要看它的成员变量。
类的结构如下:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
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
};
-
ISA
:继承自objc_object
,指向元类。 -
superclass
:指向父类。 -
cache
:方法缓存,当调用一次方法后就会缓存进vtable
中,加速下次调用。 -
bits
:具体类信息(成员变量、属性、方法)。
在源码中能找到class_rw_t
中封装了获取methods
、properties
、protocols
的方法。而class_rw_t
是存在bits
中的。那么怎么通过objc_class
获取bits
数据呢?
3.2 类的结构内存计算
既然类的底层数据是结构体,那么只要找到首地址通过偏移就能得到bits
数据的地址。ISA
和superclass
都是结构体指针分别占用8
字节,那么cache
占多大空间呢?
cache_t
中内容很多包括很多函数和static
的常量(不占结构体空间),其实只需要关注成员变量即可。
cache_t
结构如下:
struct cache_t {
private:
explicit_atomic _bucketsAndMaybeMask; //unsigned long 8字节
union {
struct {
explicit_atomic _maybeMask;//uint32_t 4字节
#if __LP64__
uint16_t _flags;//2字节
#endif
uint16_t _occupied;//2字节
};
explicit_atomic _originalPreoptCache;//指针 8字节
};
};
cache_t
包含两部分_bucketsAndMaybeMask
(unsigned long
8字节)与联合体,联合体中有一个_originalPreoptCache
(指针 8字节)与结构体(_originalPreoptCache
与结构体只需要计算一个,共用一块内存)。所以cache_t
大小为16
字节。
那么只需要类的首地址偏移32
字节(0x20 = ISA(8) + superclass (8) + cache (16)
)就能得到bits
的地址。
指针的步长与指针类型有关。
3.3 lldb分析类的结构
3.3.1 ISA
- 类的
isa
就是一个纯指针,指向元类。
3.3.2 superclass
- 类的
superclass
就是类的父类。
3.3.3 cache
cache
方法缓存相关的内容cache。
3.3.4 bits
bits
的类型是class_data_bits_t
结构如下:
struct class_data_bits_t {
uintptr_t bits;
};
在源码中有class_rw_t * plus custom rr/alloc flags
注释,也就是说class_data_bits_t
的核心是class_rw_t
。查找源码发现data()
返回的是class_rw_t*
类型。
data()
data()
实现如下:
#if __LP64__
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#else
#define FAST_DATA_MASK 0xfffffffcUL
#endif
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
-
64
位下data()
占用44
位[3~46]
,32
位下data()
占用30
位[2~31]
。 - 所以也就是
bits
(class_data_bits_t
)的[3~46]
/[2~31]
位是data()
(class_rw_t
)。
同理源码中有以下代码:
#if __LP64__
// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE (1UL<<1)
// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR (1UL<<2)
// data pointer
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#else
// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE (1UL<<1)
#define FAST_DATA_MASK 0xfffffffcUL
#endif // __LP64__
bool isSwiftStable() {
return getBit(FAST_IS_SWIFT_STABLE);
}
bool isSwiftLegacy() {
return getBit(FAST_IS_SWIFT_LEGACY);
}
bool hasCustomRR() const {
return !bits.getBit(FAST_HAS_DEFAULT_RR);
}
-
FAST_IS_SWIFT_LEGACY
:第0
位类是否来自稳定的Swift ABI
的Swift
类。(遗留的类) -
FAST_IS_SWIFT_STABLE
:第1
位类是否来自稳定的Swift ABI
的Swift
类。 -
FAST_HAS_DEFAULT_RR
:第2
位判断当前类或者父类是否含有默认的retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
方法。(仅64
位)
前面已经得出结论类的首地址偏移32
字节就能得到bits
的地址:
-
首地址 + 偏移
得到bits
的地址$8
。 - 强转
bits
地址($8
)为class_data_bits_t
指针($9
)。 -
bits
指针($9
)调用data()
函数获取class_rw_t
指针($10
)。 - 打印
class_rw_t
指针所指向的值(*$10
)。
class_rw_t
既然data()
是class_rw_t
结构,它的内存结构如下:
struct class_rw_t {
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
};
-
flags
: -
witness
: -
index
:32
位下有效,记录类在数组中的索引。 -
ro_or_rw_ext
:存储属性,方法,协议,成员变量。 -
firstSubclass
:第一个子类。
在上面的调试中firstSubclass
为nil
是因为它没有被使用。是懒加载类,如果使用了或者实现了+ load
方法则会指向子类。 -
nextSiblingClass
:相邻类。
分析到这里显然核心就是ro_or_rw_ext
了,查看class_rw_t
源码发现提供了methods、properties、protocols
方法。
修改HPObject
以及添加方法,文件如下:
HPObject
:
@interface HPObject : NSObject {
int height;
NSString *sex;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
HPObject+Additions1
:
@interface HPObject (Additions1)
- (void)additions1InstanceMethod;
+ (void)additions1ClassMethod;
@end
HPObject+additions2
:
@interface HPObject (additions2)
- (void)additions2InstanceMethod;
+ (void)additions2ClassMethod;
@end
⚠️:方法要有对应的实现。
properties()
(lldb) x/6gx HPObject.class
0x1000083d8: 0x00000001000083b0 0x0000000100358140
0x1000083e8: 0x000000010034f360 0x0000803400000000
0x1000083f8: 0x00000001032babd4 0x00000001000ac920
(lldb) p (class_data_bits_t *)0x1000083f8
(class_data_bits_t *) $1 = 0x00000001000083f8
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001032babd0
(lldb) p $2->properties()
(const property_array_t) $3 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100008308
}
arrayAndFlag = 4295000840
}
}
}
通过properties
获取到的属性是一个property_array_t
,结构如下:
class property_array_t :
public list_array_tt
{
typedef list_array_tt Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
property_array_t
继承自list_array_tt
是一个两层结构property_list_t < property_t>
。list_array_tt
中有iterator
意味着它有遍历能力。
通过list
能获取到RawPtr
:
(lldb) p $3.list
(const RawPtr) $4 = {
ptr = 0x0000000100008308
}
通过访问ptr
能够获取到property_list_t
数组:
(lldb) p $4.ptr
(property_list_t *const) $5 = 0x0000000100008308
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt = (entsizeAndFlags = 16, count = 2)
}
$5
就相当于迭代器了,property_list_t
源码结构如下:
struct property_list_t : entsize_list_tt {
};
property_list_t
是一个空实现继承自entsize_list_tt
,entsize_list_tt
中有一个get
方法:
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
};
所以可以根据get
方法获取元素:
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "age", attributes = "Ti,N,V_age")
(lldb) p $6.get(2)
Assertion failed: (i < count), function get, file /Volumes/HOTPOTCAT/sourcecode/objc4/objc4-818.2/runtime/objc-runtime-new.h, line 625.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
这个时候并没有成员变量height
和sex
。只有name
和age
两个属性。
methods()
与properties
相同,通过class_rw_t
的methods
方法获取方法:
(lldb) p $2->methods()
(const method_array_t) $3 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100008098
}
arrayAndFlag = 4295000216
}
}
}
(lldb) p $3.list.ptr
(method_list_t *const) $4 = 0x0000000100008098
(lldb) p *$4
(method_list_t) $5 = {
entsize_list_tt = (entsizeAndFlags = 27, count = 8)
}
(lldb) p $5.get(0)
(method_t) $6 = {}
- 看到有
8
个方法。 - 由于
method_list_t
也是继承自entsize_list_tt
,所以直接通过get()
获取,结果返回空。
那么分别看下property_t
和method_t
的实现。
property_t:
struct property_t {
const char *name;
const char *attributes;
};
method_t:
struct method_t {
//......
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
//......
};
两者的区别是property_t
有成员变量,method_t
没有成员变量。索引method_t
打印为空。但是method_t
中有一个结构体big
中有name
和imp
,并且提供了一个big
方法,所以可以通过big
方法获取:
(lldb) p $5.get(0).big()
(method_t::big) $7 = {
name = "additions1InstanceMethod"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003c30 (HPObjcTest`-[HPObject(Additions1) additions1InstanceMethod])
}
(lldb) p $5.get(1).big()
(method_t::big) $8 = {
name = "instanceMethod"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003c50 (HPObjcTest`-[HPObject instanceMethod])
}
(lldb) p $5.get(2).big()
(method_t::big) $9 = {
name = "additions2InstanceMethod"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003d50 (HPObjcTest`-[HPObject(additions2) additions2InstanceMethod])
}
(lldb) p $5.get(3).big()
(method_t::big) $10 = {
name = ".cxx_destruct"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003d00 (HPObjcTest`-[HPObject .cxx_destruct])
}
(lldb) p $5.get(4).big()
(method_t::big) $11 = {
name = "name"
types = 0x0000000100003f55 "@16@0:8"
imp = 0x0000000100003c60 (HPObjcTest`-[HPObject name])
}
(lldb) p $5.get(5).big()
(method_t::big) $12 = {
name = "setName:"
types = 0x0000000100003f5d "v24@0:8@16"
imp = 0x0000000100003c90 (HPObjcTest`-[HPObject setName:])
}
(lldb) p $5.get(6).big()
(method_t::big) $13 = {
name = "age"
types = 0x0000000100003f68 "i16@0:8"
imp = 0x0000000100003cc0 (HPObjcTest`-[HPObject age])
}
(lldb) p $5.get(7).big()
(method_t::big) $14 = {
name = "setAge:"
types = 0x0000000100003f70 "v20@0:8i16"
imp = 0x0000000100003ce0 (HPObjcTest`-[HPObject setAge:])
}
- 除了两个属性的
4
个setter + getter
方法外还有类和分类的实例方法以及. cxx_destruct
。
protocols()
(lldb) x/6gx HPObject.class
0x1000088a0: 0x0000000100008878 0x000000010036b140
0x1000088b0: 0x0000000100362360 0x0000803c00000000
0x1000088c0: 0x0000000100637d64 0x00000001000b9980
(lldb) p (class_data_bits_t *)0x1000088c0
(class_data_bits_t *) $16 = 0x00000001000088c0
(lldb) p $16->data()
(class_rw_t *) $17 = 0x0000000100637d60
(lldb) p $17->protocols()
(const protocol_array_t) $18 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100008678
}
arrayAndFlag = 4295001720
}
}
}
protocols()
获取的是protocol_array_t
:
class protocol_array_t :
public list_array_tt
{
typedef list_array_tt Super;
public:
protocol_array_t() : Super() { }
protocol_array_t(protocol_list_t *l) : Super(l) { }
};
继承自list_array_tt
与其属性和方法一致。通过list.ptr
能获取到protocol_list_t
:
(lldb) p $18.list.ptr
(protocol_list_t *const) $19 = 0x0000000100008678
(lldb) p *$19
(protocol_list_t) $20 = (count = 1, list = protocol_ref_t [] @ 0x00007fb14e4f68b8)
结构如下:
struct protocol_list_t {
// count is pointer-sized by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
};
它没有继承自entsize_list_tt
。protocol_ref_t
是个无符号长整形:
typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
尝试用get
函数获取元素:
(lldb) p $20.get(0).big()
error: :1:5: no member named 'get' in 'protocol_list_t'
$20.get(0).big()
~~~ ^
(lldb) p $20.get(0)
error: :1:5: no member named 'get' in 'protocol_list_t'
$20.get(0)
~~~ ^
果然都失败了,可以看到protocol_ref_t
既然不是个结构体所以没有名字相关的数据。没有继承自entsize_list_tt
所以没有get
函数。protocol_ref_t
的注视看着与protocol_t
有关,
查看下protocol_t
的结构:
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
};
现在的问题就是protocol_ref_t
怎么转变成protocol_t
。
搜索后发现在remapProtocol
中protocol_ref_t
直接强转成了protocol_t
:
(lldb) p $20.list[0]
(protocol_ref_t) $21 = 4295002416
(lldb) p (protocol_t *)$21
(protocol_t *) $22 = 0x0000000100008930
(lldb) p *$22
(protocol_t) $23 = {
objc_object = {
isa = {
bits = 4298551496
cls = Protocol
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 537318937
magic = 0
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100003c13 "HPObjectProtocol"
protocols = 0x00000001000085c8
instanceMethods = 0x00000001000085e0
classMethods = 0x0000000100008600
optionalInstanceMethods = 0x0000000000000000
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x0000000000000000
size = 96
flags = 0
_extendedMethodTypes = 0x0000000100008620
_demangledName = 0x0000000000000000
_classProperties = 0x0000000000000000
}
这样就获取到了protocol_t
中的数据。
protocol_t
数据结构获取:bits->data()->protocols().list.ptr.list[0]->(protocol_t *)强转
。
instanceMethods
(lldb) p $23.instanceMethods
(method_list_t *) $25 = 0x00000001000085e0
(lldb) p *$25
(method_list_t) $26 = {
entsize_list_tt = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $26.get(0).big()
(method_t::big) $27 = {
name = "protocolInstanceMethod"
types = 0x0000000100003eb5 "v16@0:8"
imp = 0x0000000000000000
}
- 实例方法的获取与
methods
相同。
protocols
可以看到protocol_t
中有protocols
那么它是什么数据呢?
(lldb) p $23.protocols
(protocol_list_t *) $24 = 0x00000001000085c8
(lldb) p *$24
(protocol_list_t) $28 = (count = 1, list = protocol_ref_t [] @ 0x00007fb151e12bf8)
(lldb) p $28.list[0]
(protocol_ref_t) $31 = 4295002320
(lldb) p (protocol_t *)$31
(protocol_t *) $32 = 0x00000001000088d0
(lldb) p *$32
(protocol_t) $33 = {
objc_object = {
isa = {
bits = 0
cls = nil
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100003c0a "NSObject"
protocols = 0x0000000000000000
instanceMethods = 0x00000001000082f0
classMethods = 0x0000000000000000
optionalInstanceMethods = 0x00000001000084c0
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x00000001000084e0
size = 96
flags = 0
_extendedMethodTypes = 0x0000000100008528
_demangledName = 0x0000000000000000
_classProperties = 0x0000000000000000
}
可以看到是属于NSObject
的。这个时候protocols
就没有指向了。instanceMethods
还仍然有数据:
(lldb) p $33.instanceMethods
(method_list_t *) $34 = 0x00000001000082f0
(lldb) p *$34
(method_list_t) $35 = {
entsize_list_tt = (entsizeAndFlags = 24, count = 19)
}
(lldb) p $35.get(0).big()
(method_t::big) $36 = {
name = "isEqual:"
types = 0x0000000100003ebd "c24@0:8@16"
imp = 0x0000000000000000
}
(lldb) p $35.get(1).big()
(method_t::big) $37 = {
name = "class"
types = 0x0000000100003ec8 "#16@0:8"
imp = 0x0000000000000000
}
(lldb) p $35.get(2).big()
(method_t::big) $38 = {
name = "self"
types = 0x0000000100003ed0 "@16@0:8"
imp = 0x0000000000000000
}
可以看到有19
个方法,都有哪些呢?如下图:
没有
@optional
的debugDescription
。这也正常因为还有optionalInstanceMethods
与optionalClassMethods
。
classMethods
(lldb) p $57.get(0).big()
(method_t::big) $58 = {
name = "protocolClassMethod"
types = 0x0000000100003eb5 "v16@0:8"
imp = 0x0000000000000000
}
⚠️只要遵循协议就能关联到,不需要实现方法。本质上与对象通过isa
关联cls
相同。协议在底层也继承自objc_object
。也有isa
的数据结构。也可以添加属性。
ro()
在properties
中并没有找到实例变量,但是在class_rw_t
的methods()
附近发现了ro()
,它返回class_ro_t
类型:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
发现class_rw_t
中有ivar
。
(lldb) p $2->ro()
(const class_ro_t *) $3 = 0x0000000100008238
(lldb) p *$3
(const class_ro_t) $4 = {
flags = 388
instanceStart = 8
instanceSize = 40
reserved = 0
= {
ivarLayout = 0x0000000100003e7a "\x11\x11"
nonMetaclass = 0x0000000100003e7a
}
name = {
std::__1::atomic = "HPObject" {
Value = 0x0000000100003e71 "HPObject"
}
}
baseMethodList = 0x0000000100008098
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008280
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008308
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $4.ivars
(const ivar_list_t *const) $5 = 0x0000000100008280
(lldb) p *$5
(const ivar_list_t) $6 = {
entsize_list_tt = (entsizeAndFlags = 32, count = 4)
}
-
ivars
结构为ivar_list_t
类型,也是继承自entsize_list_tt
,所以同样应该能够根据get()
去获取ivar_t
。 - 有
4
个成员变量。
ivar_t结构如下:
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
获取实例变量:
(lldb) p $6.get(0)
(ivar_t) $7 = {
offset = 0x0000000100008340
name = 0x0000000100003ec3 "height"
type = 0x0000000100003f47 "i"
alignment_raw = 2
size = 4
}
(lldb) p $6.get(1)
(ivar_t) $8 = {
offset = 0x0000000100008348
name = 0x0000000100003eca "sex"
type = 0x0000000100003f49 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $6.get(2)
(ivar_t) $9 = {
offset = 0x0000000100008350
name = 0x0000000100003ece "_age"
type = 0x0000000100003f47 "i"
alignment_raw = 2
size = 4
}
(lldb) p $6.get(3)
(ivar_t) $10 = {
offset = 0x0000000100008358
name = 0x0000000100003ed3 "_name"
type = 0x0000000100003f49 "@\"NSString\""
alignment_raw = 3
size = 8
}
类方法
在methods()
中并没有找到类方法,那么类方法存储在哪里呢?根据isa
的走位直接进去元类查看。元类的结构也是objc_class
所以可以尝试同样的方式获取。
(lldb) x/4gx HPObject.class
0x1000083d8: 0x00000001000083b0 0x0000000100358140
0x1000083e8: 0x000000010034f360 0x0000803400000000
(lldb) x/6gx 0x00000001000083b0
0x1000083b0: 0x00000001003580f0 0x00000001003580f0
0x1000083c0: 0x0000000102930780 0x0002e03500000003
0x1000083d0: 0x00000001029303e4 0x00000001000083b0
(lldb) p (class_data_bits_t *)0x1000083d0
(class_data_bits_t *) $1 = 0x00000001000083d0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001029303e0
(lldb) p $2->methods()
(const method_array_t) $3 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100008048
}
arrayAndFlag = 4295000136
}
}
}
(lldb) p $3.list.ptr
(method_list_t *const) $4 = 0x0000000100008048
(lldb) p *$4
(method_list_t) $5 = {
entsize_list_tt = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $5.get(0).big()
(method_t::big) $6 = {
name = "additions1ClassMethod"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003c20 (HPObjcTest`+[HPObject(Additions1) additions1ClassMethod])
}
(lldb) p $5.get(1).big()
(method_t::big) $7 = {
name = "classMethod"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003c40 (HPObjcTest`+[HPObject classMethod])
}
(lldb) p $5.get(2).big()
(method_t::big) $8 = {
name = "additions2ClassMethod"
types = 0x0000000100003f3f "v16@0:8"
imp = 0x0000000100003d40 (HPObjcTest`+[HPObject(additions2) additions2ClassMethod])
}
- 通过类的
isa
找到元类。 - 元类的结构也是
objc_class
,在元类中以同样的方式获取bits
的data()
中methods()
就能获取到类方法了。 - 元类的作用就是存储类方法。(在底层没有所谓的类方法,都是对象方法。)
结论:类和分类的类方法存储在元类中。
至此,属性、方法和实例变量的结构就清晰了,结构如下图:
总结
- 属性获取:
类->bits(offset 0x20)->data()->properties().list.ptr.get(index)
- 实例方法获取:
类->bits(offset 0x20)->data()->methods().list.ptr.get(index).big()
- 成员变量获取:
类->bits(offset 0x20)->ro().ivars.get(index)
- 类方法获取:
类->isa->bits(offset 0x20)->data()->methods().list.ptr.get(index).big()
四、 __has_feature(ptrauth_calls)
在源码分析中经常看到__has_feature(ptrauth_calls)
,那么它究竟有什么作用呢?
-
__has_feature
:判断编译器是否支持某个功能 -
ptrauth_calls
:指针身份验证,针对arm64e
架构;使用Apple A12
或更高版本A
系列处理器的设备(iPhoneX
以后,不包括X
)支持arm64e
架构。
也就是iPhone X
以上的设备支持指针验证。
Devices using the Apple A12 or later A-series processor — like the iPhone XS, iPhone XS Max, and iPhone XR — support the arm64e architecture. To test your adoption, you have to run your app on one of these devices. You can’t test using the Simulator.
参考链接
可以在Build Settings -> Architectures
中配置:
PAC
这里就引出了一个问题,什么是PAC
?
PAC
即Pointer Authentication
,它的目的即检测和保护地址不被意外或恶意修改,使得应用执行更加安全。PAC
特性是由硬件提供的,保护了函数调用期间,栈空间和地址的安全。
具体可以参考:arm64e与PAC