前言
在开发中,我们经常定义各种各样的类方法,实例方法,但是你有思考过它们在底层是如何分布的吗?类方法和实例方法是在一起吗?如果不在一起,他们都分别存放在哪里呐?带着问题开始我们今天的探索。
创建一个简单的类
这里创建一个简单地ZZPerson类
@interface ZZPerson : NSObject
- (void)testInstanceMethod0;
- (void)testInstanceMethod1;
+ (void)testClassMethod0;
+ (void)testClassMethod1;
@end
@implementation ZZPerson
- (void)testInstanceMethod0
{
NSLog(@"我是实例方法0");
}
- (void)testInstanceMethod1
{
NSLog(@"我是实例方法1");
}
+ (void)testClassMethod0
{
NSLog(@"我是类方法0");
}
+ (void)testClassMethod1
{
NSLog(@"我是类方法1");
}
@end
这部分代码比较简单,主要是定义了2个实例方法和2个类方法。
通过runtime
方式探索
- 操作一:获取一个类对象的所有方法
//获取一个类中的所有方法并输出
void zzObjc_getMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"\nMethod, name: %@", key);
}
free(methods);
}
输出结果:
2020-09-15 15:21:17.463609+0800 ZZObjc[199:50336892]
Method, name: testInstanceMethod0
2020-09-15 15:21:17.464687+0800 ZZObjc[199:50336892]
Method, name: testInstanceMethod1
通过这个方式我们只拿到了该类对象的所有实例方法,却没有输出任何类方法。这里也无法说明类方法是否存在于当前类对象中。
- 操作二:判断
sel
是否在某个类中
void zzMethodInClass(Class pClass){
/*
- (void)testInstanceMethod0;
- (void)testInstanceMethod1;
+ (void)testClassMethod0;
+ (void)testClassMethod1;
*/
const char *className = class_getName(pClass);
//获取pClass的元类
Class metaClass = objc_getMetaClass(className);
//在类对象中获取 testInstanceMethod0 实例方法
Method method1 = class_getInstanceMethod(pClass, @selector(testInstanceMethod0));
//在元类对象中获取 testInstanceMethod0 实例方法
Method method2 = class_getInstanceMethod(metaClass, @selector(testInstanceMethod0));
//在类对象中获取 testClassMethod0 实例方法
Method method3 = class_getInstanceMethod(pClass, @selector(testClassMethod0));
//在元类对象中获取 testClassMethod0 实例方法
Method method4 = class_getInstanceMethod(metaClass, @selector(testClassMethod0));
NSLog(@"\n%p\n%p\n%p\n%p",method1,method2,method3,method4);
}
输出结果:
2020-09-15 15:34:46.695230+0800 KCObjc[1431:50348801]
method1:0x1000021d8
method2:0x0
method3:0x0
method4:0x100002158
这里反映出来的结论是:实例方法在类对象里,类方法在元类对象。但是这里让人疑惑的是:+ (void)testClassMethod0;
明明就是一个类方法,这里确是通过Method class_getInstanceMethod(Class cls, SEL sel)
查询,为什么不是Method class_getClassMethod(Class cls, SEL sel)
???
带着疑问继续看。
这里我们修改一下method3
的获取方式:
// Method method3 = class_getInstanceMethod(pClass, @selector(testClassMethod0));
Method method3 = class_getClassMethod(pClass, @selector(testClassMethod0));
这里我们通过class_getClassMethod()
获取method3
2020-09-15 15:48:22.384323+0800 ZZObjc[2548:50360370]
method1:0x1000021d8
method2:0x0
method3:0x100002158
method4:0x100002158
此时method3
也有值了,而且地址跟method4
一样。这也说明testClassMethod0
方法是唯一的。
接下来我们看下class_getClassMethod()
的源码实现:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
这里同样调用了class_getInstanceMethod ()
,并且将cls
的元类对象传给了该方法。这里跟我们从外部先获取到当前类对象
的元类对象
,然后再调用class_getInstanceMethod
其实是一样的。这里同样也说明要获取类方法确实需要跟元类打交道。
通过LLDB验证
- 获取
ZZPerson类对象
的方法列表
(lldb) p/x ZZPerson.class
(Class) $0 = 0x0000000100002290 ZZPerson
通过指针偏移获取class_data_bits_t bits
,根据类对象的内存布局,这里需要偏移32 bytes
(0x20)来获取bits
,即0x0000000100002290+0x20 = 0x1000022B0
,并读取bits->data()
,来获取该对象的具体内容
(lldb) p (class_data_bits_t *)0x1000022B0
(class_data_bits_t *) $1 = 0x00000001000022b0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001010811d0
具体内容存贮在class_rw_t *
类型指针下,读取该指针:
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975872
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
读取该class_rw_t
结构体内的methods()
:
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000021c8
arrayAndFlag = 4294975944
}
}
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000021c8
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "testInstanceMethod0"
types = 0x0000000100000fa2 "v16@0:8"
imp = 0x0000000100000d50 (KCObjc`-[ZZPerson testInstanceMethod0])
}
}
}
(lldb)
到此,我们可以看到当前列表的count = 2
,first
为testInstanceMethod0
方法信息,我们把方法全部输出:
(lldb) p $6.get(0)
(method_t) $7 = {
name = "testInstanceMethod0"
types = 0x0000000100000fa2 "v16@0:8"
imp = 0x0000000100000d50 (KCObjc`-[ZZPerson testInstanceMethod0])
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = "testInstanceMethod1"
types = 0x0000000100000fa2 "v16@0:8"
imp = 0x0000000100000d80 (KCObjc`-[ZZPerson testInstanceMethod1])
}
此时,2个实例方法就这样赤裸裸的暴露在了眼前!同样也证实了类对象的方法列表中确实只存在实例方法。
- 获取
ZZPerson 元类对象
的方法列表
获取列元类对象isa
:
(lldb) x/4gx ZZPerson.class
0x100002290: 0x0000000100002268 0x0000000100333140
0x1000022a0: 0x000000010032d440 0x0000801000000000
从isa
中读取shiftcls
信息(不清楚的移步NOPOINTER_ISA)
(lldb) p/x 0x0000000100002268 & 0x0000000ffffffff8ULL
(unsigned long long) $1 = 0x0000000100002268
(lldb) po $1
ZZPerson
(lldb)
此时,我们就拿到了元类对象,元类的本质也是objc_class
结构体,所有获取信息的方式跟类对象是一样的。
(lldb) p (class_data_bits_t *)0x100002288
(class_data_bits_t *) $2 = 0x0000000100002288
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010182e540
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975744
}
firstSubclass = nil
nextSiblingClass = 0x00007fff89236c60
}
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002148
arrayAndFlag = 4294975816
}
}
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002148
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "testClassMethod0"
types = 0x0000000100000fa2 "v16@0:8"
imp = 0x0000000100000cf0 (KCObjc`+[ZZPerson testClassMethod0])
}
}
}
(lldb) p $7.get(0)
(method_t) $8 = {
name = "testClassMethod0"
types = 0x0000000100000fa2 "v16@0:8"
imp = 0x0000000100000cf0 (KCObjc`+[ZZPerson testClassMethod0])
}
(lldb) p $7.get(1)
(method_t) $9 = {
name = "testClassMethod1"
types = 0x0000000100000fa2 "v16@0:8"
imp = 0x0000000100000d20 (KCObjc`+[ZZPerson testClassMethod1])
}
两个自定义的类方法就赤裸裸的呈现在了面前。
补充
总结
实例方法存在类对象中,类方法存在元类对象中,类方法是元类对象的实例方法。
网友评论