iOS进阶-04类的结构
2020-02-05 本文已影响0人
ricefun
初观类的结构
先看源码:
NSObjectd定义
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Class定义
typedef struct objc_class *Class;
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_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
assert(isFuture() || isRealized());
data()->setFlags(set);
}
...
...
...
objc_object定义
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
上述源码总结:
- Class是objc_class结构体的指针(注意看*)
- objc_class 又是继承于objc_object的结构体
- objc_object结构体中只有isa 一个属性
类的内部结构
先上调试代码:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject {
NSString *hobby;//成员变量
}
@property (nonatomic,copy) NSString *nickName;//属性
- (void)eat;
+ (void)sleep;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
- (void)eat {
}
+ (void)sleep {
}
@end
#####
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"hello world!");
}
return 0;
}
在NSLog 打断点进行LLDB调试,获取class_data_bits_t bits
内存情况:
1.打印当前Person类的内存情况
(lldb) x/4gx Person.class
0x1000026d0: 0x001d8001000026a9 0x0000000100b38140
0x1000026e0: 0x00000001003db270 0x0000000000000000
我们已经知道0x001d8001000026a9
是isa,又根据下面objc_class 源码可知:0x0000000100b38140
是superclass,
0x00000001003db270
是cache,但是bits我们不知道,反正不可能是0x0000000000000000
//objc_class 源码
struct objc_class : objc_object {
// Class ISA; //8个字节
Class superclass;//8个字节
cache_t cache; //16个字节
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
...
...
}
如何获取到class_data_bits_t bits
?我在上面objc_class源码上标ISA 、superclass、cache的字节占用大小三者加起来刚好是32个字节,换成16进制就是20;所以bits就是0x1000026d0
偏移20字节位0x1000026f0
。所以继续LLDB调试:
为什么是8、8、16,我在OC底层原理探索-03中放过一张数据类型占用字节大小的图,请自行查看
(lldb) p (class_data_bits_t *)0x1000026f0//需要使用class_data_bits_t *)进行强转
(class_data_bits_t *) $1 = 0x00000001000026f0
(lldb) p $1->data()//根据源码方法 bits.data();获取class_rw_t,就是下面$3
(class_rw_t *) $3 = 0x0000000102d2a230
(lldb) p *$3//打印class_rw_t的内部结构
(class_rw_t) $4 = {
flags = 2148139008
version = 0
ro = 0x00000001000024e8
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002420
arrayAndFlag = 4294976544
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000024d0
arrayAndFlag = 4294976720
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
//class_rw_t 部分源码
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
...
...
}
对比 class_rw_t
源码,是不是惊人的相似,说明class_rw_t
数据我们已经拿到了;既然能拿到class_rw_t
,其内部的class_ro_t
是不是也能拿到。继续LLDB调试:
(lldb) p $4.ro
(const class_ro_t *) $6 = 0x00000001000024e8
(lldb) p $6
(const class_ro_t *) $6 = 0x00000001000024e8
(lldb) p *$6
(const class_ro_t) $7 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001dd3 "\x02"
name = 0x0000000100001dcc "Person"
baseMethodList = 0x0000000100002420
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002488
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000024d0
}
class_ro_t 源码
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
对比 class_ro_t
源码,是不是同样的相似,说明class_ro_t
数据我们已经拿到了;同样我们可以打印其内部的baseProtocols 、ivars 、baseMethodList
,继续LLDB调试:
(lldb) p $7.baseProperties
(property_list_t *const) $8 = 0x00000001000024d0
(lldb) p *$8//打印属性list
(property_list_t) $9 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1//只有一个属性,即nickName
first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
}
}
(lldb) p $9.get(0)//打印第一个属性,存在
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $9.get(1) //打印第二个属性,就报错了
Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) p $7.ivars
(const ivar_list_t *const) $11 = 0x0000000100002488
(lldb) p *$11 //打印ivars 成员变量数组
(const ivar_list_t) $12 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2//有两个hobby和nickName
first = {
offset = 0x00000001000025f8
name = 0x0000000100001e18 "hobby"
type = 0x0000000100001eb5 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $12.get(0)//打印第1个ivar
(ivar_t) $13 = {
offset = 0x00000001000025f8
name = 0x0000000100001e18 "hobby"
type = 0x0000000100001eb5 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $12.get(1)//打印第2个ivar
(ivar_t) $14 = {
offset = 0x0000000100002600
name = 0x0000000100001e68 "_nickName"
type = 0x0000000100001eb5 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.baseMethodList
(method_list_t *const) $15 = 0x0000000100002420
(lldb) p *$15//打印实例方法列表
(method_list_t) $16 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4//有4个?分别是 eat、nickName的get/set方法,还有一个系统自带的cxx_destruct方法
first = {
name = "eat"
types = 0x0000000100001e72 "v16@0:8"
imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
}
}
}
(lldb) p $16.get(0)
(method_t) $17 = {
name = "eat"
types = 0x0000000100001e72 "v16@0:8"
imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
}
(lldb) p $16.get(1)
(method_t) $18 = {
name = ".cxx_destruct"
types = 0x0000000100001e72 "v16@0:8"
imp = 0x0000000100001ce0 (LGTest`-[Person .cxx_destruct] at Person.m:10)
}
(lldb) p $16.get(2)
(method_t) $19 = {
name = "setNickName:"
types = 0x0000000100001e82 "v24@0:8@16"
imp = 0x0000000100001ca0 (LGTest`-[Person setNickName:] at Person.h:15)
}
(lldb) p $16.get(3)
(method_t) $20 = {
name = "nickName"
types = 0x0000000100001e7a "@16@0:8"
imp = 0x0000000100001c70 (LGTest`-[Person nickName] at Person.h:15)
}
(lldb) p $16.get(4)//打印第5个就报错了
Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
到这里,我想各位已经对Class的结构有了初步的认识了
总结
-
objc_class
中的含有class_data_bits_t bits
-
class_data_bits_t bits
通过bits.data();
方法获取class_rw_t
-
class_rw_t
含有class_ro_t
; - 成员变量存在
class_ro_t
中的ivars
里面 - 属性存在
class_ro_t
中的baseProperties
里面,并且在ivars
中也有一份带下滑划线的成员变量
,并且会自动生成getter/set
方法存在baseMethodList
中 - 实例方法存类里面,在
class_ro_t
中的baseMethodList
里面,且包含属性的getter/set
方法还有系统实例方法.cxx_destruc
- 类方法存在元类的里面(这个暂时不讲了,探索方法其实例方法一样)
完整LLDB调试过程如下
(lldb) x/4gx Person.class
0x1000026d0: 0x001d8001000026a9 0x0000000100b38140
0x1000026e0: 0x00000001003db270 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000026f0
(class_data_bits_t *) $1 = 0x00000001000026f0
(lldb) p $1->data()
(class_rw_t *) $3 = 0x0000000102d2a230
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148139008
version = 0
ro = 0x00000001000024e8
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002420
arrayAndFlag = 4294976544
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000024d0
arrayAndFlag = 4294976720
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
(lldb) p $4.ro
(const class_ro_t *) $6 = 0x00000001000024e8
(lldb) p *$6
(const class_ro_t) $7 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001dd3 "\x02"
name = 0x0000000100001dcc "Person"
baseMethodList = 0x0000000100002420
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002488
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000024d0
}
(lldb) p $7.baseProperties
(property_list_t *const) $8 = 0x00000001000024d0
(lldb) p *$8
(property_list_t) $9 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
}
}
(lldb) p $9.get[0]
error: reference to non-static member function must be called
(lldb) p $9.get(0)
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $9.get(1)
Assertion failed: (i < count), function get, file /Users/Desktop/码/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) p $7.ivars
(const ivar_list_t *const) $11 = 0x0000000100002488
(lldb) p *$11
(const ivar_list_t) $12 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000025f8
name = 0x0000000100001e18 "hobby"
type = 0x0000000100001eb5 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $12.get(0)
(ivar_t) $13 = {
offset = 0x00000001000025f8
name = 0x0000000100001e18 "hobby"
type = 0x0000000100001eb5 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $12.get(1)
(ivar_t) $14 = {
offset = 0x0000000100002600
name = 0x0000000100001e68 "_nickName"
type = 0x0000000100001eb5 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.baseMethodList
(method_list_t *const) $15 = 0x0000000100002420
(lldb) p *$15
(method_list_t) $16 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "eat"
types = 0x0000000100001e72 "v16@0:8"
imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
}
}
}
(lldb) p $16.get(0)
(method_t) $17 = {
name = "eat"
types = 0x0000000100001e72 "v16@0:8"
imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
}
(lldb) p $16.get(1)
(method_t) $18 = {
name = ".cxx_destruct"
types = 0x0000000100001e72 "v16@0:8"
imp = 0x0000000100001ce0 (LGTest`-[Person .cxx_destruct] at Person.m:10)
}
(lldb) p $16.get(2)
(method_t) $19 = {
name = "setNickName:"
types = 0x0000000100001e82 "v24@0:8@16"
imp = 0x0000000100001ca0 (LGTest`-[Person setNickName:] at Person.h:15)
}
(lldb) p $16.get(3)
(method_t) $20 = {
name = "nickName"
types = 0x0000000100001e7a "@16@0:8"
imp = 0x0000000100001c70 (LGTest`-[Person nickName] at Person.h:15)
}
(lldb) p $16.get(4)
Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.