前言
在我们分析OC类底层原理时,经常会涉及对象、isa、类之间的关系链,以及类的继承链,例如:
1、对象isa指向类,类的isa指向元类,元类isa指向根元类,根元类isa指向根元类自己。
2、类继承自元类,元类继承自根元类,根元类继承自NSObject等。
具体的见官方的一张经典的isa,superclass图。
一、类结构
参考Objc源码objc4,类结构如下:
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; //继承自objc_object
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//..... 省略后面代码
}
结构分析:
Class ISA; 是继承自objc_object的成员变量
Class superclass; 父类
cache_t cache; 缓存方法,指针的结构体
class_data_bits_t bits;存储类相关的数据信息结构体,这个正是这篇文章来分析的。
LLDB获取class_data_bits_t数据
要想从对象内存结构中获取某个成员的内存数据,就需要涉及内存偏移,需要内存偏移就需要知道涉及的成员类型占用内存的大小,如下:
Class ISA;这是一个指针,自然占用的大小为8个byte;
Class superclass; 这也是一个指针,也是占用8个byte;
cache_t cache;从源码分析,这个结构体占用16个byte,期待后续篇幅,在此略过;
那么我们就知道要想从类中得到class_data_bits_t,那么内存需要偏移32个字节,类定义如下:
@interface WJPerson : NSObject {
NSString *hobby;
}
// isa
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
- (void)instanceMethod;
+ (void)classMethod;
@end
断点在[WJPerson alloc]处,如下:
在调试区域使用LLDB x/4gx命令查看WJPerson类内存数据如下:
(lldb) x/4gx WJPerson.class
0x1000083b8: 0x0000000100008390 0x000000010036a140
0x1000083c8: 0x00000001003623b0 0x0000802800000000
(lldb) p/x 0x1000083b8 + 0x20 //0x20: 偏移32字节
(long) $1 = 0x00000001000083d8
(lldb) p (class_data_bits_t *)0x00000001000083d8 //强制转换
(class_data_bits_t *) $2 = 0x00000001000083d8
p/x 0x1000083b8 + 0x20: 偏移32字节获取到class_data_bits_t内存,
二、获取属性properties列表
分析思路:class_data_bits_t -> class_rw_t -> properties()
获取class_rw_t
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101044940
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000504
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
从class_class_t中获取properties,class_class_t -> properties() -> list -> ptr
(lldb) p $4->properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000082e8
}
arrayAndFlag = 4295000808
}
}
}
Fix-it applied, fixed expression was:
$4.properties()
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
ptr = 0x00000001000082e8
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x00000001000082e8
ptr -> property_list_t -> get(n) 获取到具体的属性名
(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb)
由此获取到属性name,nickName
三、获取成员变量ivars列表
分析思路:class_data_bits_t -> class_rw_t -> class_ro_t -> ivars
获取class_ro_t
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000504
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $4.ro()
(const class_ro_t *) $11 = 0x00000001000081b8
(lldb) p * $11
(const class_ro_t) $12 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "WJPerson" {
Value = 0x0000000100003ea6 "WJPerson"
}
}
baseMethodList = 0x0000000100008200
baseProtocols = nil
ivars = 0x0000000100008280
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000082e8
_swiftMetadataInitializer_NEVER_USE = {}
}
class_ro_t -> ivars
(lldb) p $12.ivars
(const ivar_list_t *const) $13 = 0x0000000100008280
(lldb) p *$13
(const ivar_list_t) $14 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $14.list
error: <user expression 16>:1:5: no member named 'list' in 'ivar_list_t'
$14.list
~~~ ^
(lldb) p $14.get(0)
(ivar_t) $15 = {
offset = 0x0000000100008328
name = 0x0000000100003f3e "hobby"
type = 0x0000000100003f87 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $14.get(1)
(ivar_t) $16 = {
offset = 0x0000000100008330
name = 0x0000000100003f44 "_name"
type = 0x0000000100003f87 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $14.get(2)
(ivar_t) $17 = {
offset = 0x0000000100008338
name = 0x0000000100003f4a "_nickName"
type = 0x0000000100003f87 "@\"NSString\""
alignment_raw = 3
size = 8
}
获取成员变量总结
1.获取到成员变量hobby
2.另外发现属性name和nickName会生成一个对应的成员变量_name和_nickName;
四、获取方法methods列表
分析思路:class_data_bits_t -> class_rw_t -> methods()
4.1 获取实例方法列表
(lldb) p *$3
(class_rw_t) $18 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000504
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $18.methods()
(const method_array_t) $19 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008200
}
arrayAndFlag = 4295000576
}
}
}
(lldb) p $19.list
(const method_list_t_authed_ptr<method_list_t>) $20 = {
ptr = 0x0000000100008200
}
(lldb) p $20.ptr
(method_list_t *const) $21 = 0x0000000100008200
(lldb) p *$21
(method_list_t) $22 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 5)
}
从上得知有5个方法,那么接下来找出下每个方法,
(lldb) p $22.get(0).big()
(method_t::big) $23 = {
name = "instanceMethod"
types = 0x0000000100003f93 "v16@0:8"
imp = 0x0000000100003cf0 (KCObjcBuild`-[WJPerson instanceMethod])
}
(lldb) p $22.get(1).big()
(method_t::big) $24 = {
name = "name"
types = 0x0000000100003f9b "@16@0:8"
imp = 0x0000000100003d20 (KCObjcBuild`-[WJPerson name])
}
(lldb) p $22.get(2).big()
(method_t::big) $25 = {
name = "setName:"
types = 0x0000000100003fa3 "v24@0:8@16"
imp = 0x0000000100003d50 (KCObjcBuild`-[WJPerson setName:])
}
(lldb) p $22.get(3).big()
(method_t::big) $26 = {
name = "nickName"
types = 0x0000000100003f9b "@16@0:8"
imp = 0x0000000100003d80 (KCObjcBuild`-[WJPerson nickName])
}
(lldb) p $22.get(4).big()
(method_t::big) $27 = {
name = "setNickName:"
types = 0x0000000100003fa3 "v24@0:8@16"
imp = 0x0000000100003db0 (KCObjcBuild`-[WJPerson setNickName:])
}
如上,获取到了5个方法。
另外,调用method_list_t数组中的方法.get(0),如下:
(lldb) p $22.get(0)
(method_t) $28 = {}
发现显示不出来方法相关的数据,这是因为get()方法输出property_t的description, 从property_t结构看有name,attributes,所以在获取属性、成员变量时会输出对应的名称,但是methods有自行实现property_t即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;
};
private:
bool isSmall() const {
return ((uintptr_t)this & 1) == 1;
}
// The representation of a "small" method. This stores three
// relative offsets to the name, types, and implementation.
struct small {
// The name field either refers to a selector (in the shared
// cache) or a selref (everywhere else).
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP> imp;
bool inSharedCache() const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this));
}
};
small &small() const {
ASSERT(isSmall());
return *(struct small *)((uintptr_t)this & ~(uintptr_t)1);
}
.....//省略n代码
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
如上method_t定义,所以需要再调用big()方法根据description输出SEL name,另外,如果是M1电脑,需要使用method_list_t.get(n).big()获取。
获取方法名
4.1.1 非M1电脑获取方法名(旧款电脑)
method_list_t.get(n).big()
4.1.2 M1电脑获取方法名
method_list_t.get(n).small()
发现一个问题,我们的类方法怎么没有看到呢,大家都知道在OC层是有区分实例方法和类方法,哪里出了问题呢?既然类中没有找到,我们考虑下去其它地方找下,那我们尝试下去当前类关系最的元类里面找一下。
4.2 获取类方法列表
分析思路:获取WJPerson元类 -> class_rw_t -> methods()
获取WJPerson元类
(lldb) x/4gx WJPerson.class
0x1000083b8: 0x0000000100008390 0x000000010036a140
0x1000083c8: 0x00000001003623b0 0x0000802800000000
(lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8
//本地用的是mac 即ISA_MASK是x86_64的
//通过WJPerson的isa获取到WJPerson的元类
(long) $50 = 0x0000000100008390 //元类
按照前面讲到的类获取方法列表流程,如下:
(lldb) x/4gx 0x0000000100008390 //元类
0x100008390: 0x000000010036a0f0 0x000000010036a0f0
0x1000083a0: 0x0000000100762770 0x0002e03100000003
(lldb) p/x 0x100008390 + 0x20
(long) $51 = 0x00000001000083b0
(lldb) p (class_data_bits_t *)0x00000001000083b0
(class_data_bits_t *) $52 = 0x00000001000083b0
(lldb) p *$52
(class_data_bits_t) $53 = (bits = 4312044516)
(lldb) p $53->data()
(class_rw_t *) $54 = 0x00000001010493e0
Fix-it applied, fixed expression was:
$53.data()
(lldb) p $53.data()
(class_rw_t *) $55 = 0x00000001010493e0
(lldb) p $55.methods()
(const method_array_t) $56 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008198
}
arrayAndFlag = 4295000472
}
}
}
Fix-it applied, fixed expression was:
$55->methods()
(lldb) p $56.list
(const method_list_t_authed_ptr<method_list_t>) $57 = {
ptr = 0x0000000100008198
}
(lldb) p $57.ptr
(method_list_t *const) $58 = 0x0000000100008198
(lldb) p *$58
(method_list_t) $59 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $59.get(0).bit()
error: <user expression 68>:1:12: no member named 'bit' in 'method_t'
$59.get(0).bit()
~~~~~~~~~~ ^
(lldb) p $59.get(0).big()
(method_t::big) $60 = {
name = "classMethod"
types = 0x0000000100003f93 "v16@0:8"
imp = 0x0000000100003cc0 (KCObjcBuild`+[WJPerson classMethod])
}
得到元类中的方法列表中的classMethod,即是OC层WJPerson类方法+ (void)classMethod,由此可见OC层的类方法是存在元类中。
五、总结
1.在OC底层不存在类方法,所有方法都是实例方法,从另一方面也符合即万物皆对象。
2.类方法存储在元类中的方法列表中。
3.类结构中isa、superclass、chche、bits 成员变量,在对bits 探究过程中发现bits存储着大家熟悉的属性列表、成员变量列表、方法列表、协议列表等。