1.在iOS中,类的结构是什么样的呢?
main.m中首先有这样一段代码(objc源码中):
我们cd到当前路径后输入命令行:clang -rewrite-objc main.m -o main.cpp获得c++文件,打开文件,我们拉到最下面的代码,就是oc经过编译后的代码:
-
在上图中,pClass使用Class类型接收,说明pClass是Class类型的!我们找到Class的定义:
typedef struct objc_class *Class; struct objc_class { Class _Nonnull isa __attribute__((deprecated)); } __attribute__((unavailable));
-
oh my god! 废弃了,多么痛的领悟!没关系,我们去找找有没有objc_class的定义!
-
我们现在点进去看看源码:
struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; struct objc_class : objc_object { // Class ISA; 隐藏的isa,来源于objc_object,是继承来的一个东西. Class superclass; //父类 cache_t cache; class_data_bits_t bits; class_rw_t *data() { return bits.data(); } /** objc_class居然是继承了objc_object的一个结构体(万物皆对象). 说到万物皆对象,我们的大部分类不是继承自NSObject吗? 我们也去看看NSObject的定义: @interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; } 你看,是不是和 struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; } 一样呢? 以上就是这个结构体的定义内容,下面的方法以及省略的很多方法都是一些相关的设置 */ void setData(class_rw_t *newData) { bits.setData(newData); } ...... }
2.在类中,属性以及方法是怎么存储的?
我们知道了类的结构,就是继承了objc_object结构体的objc_class的结构体!那么我们在类里面生命的属性,方法等内容又是怎么存储的呢?
-
首先看几个结构截图:
-
1.objc_class : objc_object {}
-
2.cache_t结构
-
3.class_rw_t结构
-
4.我们在WMPerson类里面作如下操作
@interface WMPerson : NSObject { NSString *myStyle;//实例变量 } //属性 @property (nonatomic, copy) NSString *nickName; - (void)instanceMethodTest; + (void)ClassMethodTest; @end #import "WMPerson.h" @implementation WMPerson //对象方法 - (void)instanceMethodTest { NSLog(@"WMPerson instanceMethodTest!"); } //类方法 + (void)ClassMethodTest { NSLog(@"WMPerson ClassMethodTest!"); } @end
-
5.我们运行项目并将断点断到如图处,使用lldb指令打印内存结构得到如图的打印结果!0x10000458(因为多次运行项目,我们此篇文章中,x/4gx pClass指令打印出来的这两个指针是会变的,提前说一下,因为类的内存地址分配是会变的)指针指向cache_t的首地址!(下面的截图是未设置属性及方法时截取的,所以bits内存地址指针为0)
-
根据上面的一些内容,我们可以知道下图:
-
-
我们经过内存偏移得到bits指针,我们就是要通过指针来调用data方法获取里面的内容,看看里面存了什么内容!我们对WMPerson作如下处理,运行并通过lldb打印我们想要看到的内容:
//对WMPerson作如下处理: @interface WMPerson : NSObject{ NSString *myStyle; } @property (nonatomic, copy) NSString *nickName; @end (lldb) x/4gx pClass 0x100001478: 0x001d800100001451 0x0000000100b36140 0x100001488: 0x0000000100fcc5c0 0x0000000100000003 (lldb) p (class_data_bits_t *)0x100001498 (class_data_bits_t *) $8 = 0x0000000100001498 (lldb) p $8->data() (class_rw_t *) $9 = 0x0000000100fcbef0 (lldb) p *$9 (class_rw_t) $10 = { flags = 2148139008 version = 0 ro = 0x0000000100001260 methods = { list_array_tt<method_t, method_list_t> = { = { list = 0x0000000100001198 arrayAndFlag = 4294971800 } } } properties = { list_array_tt<property_t, property_list_t> = { = { list = 0x0000000100001248 arrayAndFlag = 4294971976 } } } protocols = { list_array_tt<unsigned long, protocol_list_t> = { = { list = 0x0000000000000000 arrayAndFlag = 0 } } } firstSubclass = nil nextSiblingClass = NSUUID demangledName = 0x0000000000000000 } (lldb) p $9.properties (property_array_t) $11 = { list_array_tt<property_t, property_list_t> = { = { list = 0x0000000100001248 arrayAndFlag = 4294971976 } } } Fix-it applied, fixed expression was: $9->properties (lldb) p $11.list (property_list_t *) $12 = 0x0000000100001248 (lldb) p $12.first (property_t) $13 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName") Fix-it applied, fixed expression was: $12->first /** 此时我们在通过p $12.second或者p $12.get[1]等其他方法, 但是就是读不出我们的myStyle属性. 这里解释一下,其实我们的属性是不存在这里面的,我们在(lldb) p *$9打印后的结果中我们可以看到一个 ro = 0x0000000100001260 的值,我们去打印这个东西看看里面是什么 */ //这里我们重新运行打印一下(下面接着打印到class_rw_t处开始): (lldb) p $3.ro (const class_ro_t *) $4 = 0x0000000100001260 (lldb) p *$4 (const class_ro_t) $5 = { flags = 388 instanceStart = 8 instanceSize = 24 reserved = 0 ivarLayout = 0x0000000100000f09 "\x02" name = 0x0000000100000f00 "WMPerson" baseMethodList = 0x0000000100001198 baseProtocols = 0x0000000000000000 ivars = 0x0000000100001200 weakIvarLayout = 0x0000000000000000 baseProperties = 0x0000000100001248 } (lldb) p $5.baseProperties (property_list_t *const) $6 = 0x0000000100001248 (lldb) p *$6 (property_list_t) $7 = { entsize_list_tt<property_t, property_list_t, 0> = { entsizeAndFlags = 16 count = 1 first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName") } } /** 我们在此处看到first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName") 那么,我们的myStyle哪去了呢? 在指令(lldb) p *$4打印得到的(const class_ro_t) $5中,我们可以看到 ivars = 0x0000000100001200 的值 我们再打印看一下这个里面存的是不是我们想要的东西 */ (lldb) p $5.ivars (const ivar_list_t *const) $8 = 0x0000000100001200 (lldb) p *$8 (const ivar_list_t) $9 = { entsize_list_tt<ivar_t, ivar_list_t, 0> = { entsizeAndFlags = 32 count = 2 //此处count = 2;说明什么呢? first = { offset = 0x0000000100001430 name = 0x0000000100000f5b "myStyle" type = 0x0000000100000fa0 "@\"NSString\"" alignment_raw = 3 size = 8 } } } /** 我们看到了myStyle成员变量, 并且在entsize_list_tt二维数组里面count = 2,是不是有两个呢? 我们下面再打印: */ (lldb) p $8.get(1) (ivar_t) $10 = { offset = 0x0000000100001438 name = 0x0000000100000f63 "_nickName" type = 0x0000000100000fa0 "@\"NSString\"" alignment_raw = 3 size = 8 } /** 哇哇,带下划线的成员变量_nickName,这是不是也说明,我们使用property声明属性,会自动生成一个带下划线的成员变量呢!!! */
-
自此我们看到了属性在类底层的存储,那么方法有事怎么存储的呢???我们重新运行,再次lldb打印:按照上面的流程重新走一遍,当我们获取到 const class_ro_t 时,在里面的键值对中我们可以找到 aseMethodList = 0x0000000100001198 ,是不是存在这里面呢?我们去看!
//我们获取到的 const class_ro_t: (const class_ro_t) $12 = { flags = 388 instanceStart = 8 instanceSize = 24 reserved = 0 ivarLayout = 0x0000000100000f09 "\x02" name = 0x0000000100000f00 "WMPerson" baseMethodList = 0x0000000100001198 baseProtocols = 0x0000000000000000 ivars = 0x0000000100001200 weakIvarLayout = 0x0000000000000000 baseProperties = 0x0000000100001248 } (lldb) p $12.baseMethodList (method_list_t *const) $13 = 0x0000000100001198 (lldb) p *$13 (method_list_t) $14 = { entsize_list_tt<method_t, method_list_t, 3> = { entsizeAndFlags = 26 count = 4 //count为4,说明这里面存了四个方法 first = { name = "instanceMethodTest" //对象方法 types = 0x0000000100000f85 "v16@0:8" //参数及返回值类型 imp = 0x0000000100000ae0 (LGTest -[WMPerson instanceMethodTest] at WMPerson.m:12) //imp指针 } } } /** count = 4,说明这里面存了四个方法,我们明明只写了两个方法, 一个是对象方法instanceMethodTest, 一个是类方法ClassMethodTest! 我们接着把四个值都打印出来看个究竟! */ (lldb) p $14.get(0) (method_t) $15 = { name = "instanceMethodTest" types = 0x0000000100000f85 "v16@0:8" imp = 0x0000000100000ae0 (LGTest -[WMPerson instanceMethodTest] at WMPerson.m:12) } (lldb) p $14.get(1) (method_t) $16 = { name = ".cxx_destruct" types = 0x0000000100000f85 "v16@0:8" imp = 0x0000000100000bb0 (LGTest -[WMPerson .cxx_destruct] at WMPerson.m:10) } (lldb) p $14.get(2) (method_t) $17 = { name = "nickName" types = 0x0000000100000f8d "@16@0:8" imp = 0x0000000100000b40 (LGTest -[WMPerson nickName] at WMPerson.h:16) } (lldb) p $14.get(3) (method_t) $18 = { name = "setNickName:" types = 0x0000000100000f95 "v24@0:8@16" imp = 0x0000000100000b70 (LGTest -[WMPerson setNickName:] at WMPerson.h:16) } /** $14.get(0)是我们的对象方法 $14.get(1)是系统的c++方法 $14.get(2)是属性nickName的getter $14.get(3)是属性nickName的setter 从这里我们是不是看出来,property属性,底层会为我们自动生成setter和getter 而成员变量myStyle则没有setter和getter 还有一个,我们没有看到类方法ClassMethodTest?类方法没有存在类里面吗? */
-
经过上面一顿操作,我们看到了对象方法以及属性的getter和setter存储,但是我们并未看到类方法存储在哪里?先面我们换一种方法去看:
/** 实现以下两个方法: 因为如果我们的类如果拥有这个方法,我们就可以通过API获取当前方法实现的指针; */ void testInstanceMethod_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className);//元类 //调用API:class_getInstanceMethod获取类和元类中的对象方法,看能否找到该对象方法 Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethodTest)); Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethodTest)); //调用API:class_getInstanceMethod获取类和元类中的对象方法,看能否找到该类方法 Method method3 = class_getInstanceMethod(pClass, @selector(ClassMethodTest)); Method method4 = class_getInstanceMethod(metaClass, @selector(ClassMethodTest)); NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4); NSLog(@"%s",__func__); } void testClassMethod_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className);//元类 //调用API:class_getClassMethod获取类和元类中的类方法,看能否找到该对象方法 Method method1 = class_getClassMethod(pClass, @selector(instanceMethodTest)); Method method2 = class_getClassMethod(metaClass, @selector(instanceMethodTest)); //调用API:class_getClassMethod获取类和元类中的类方法,看能否找到该类方法 Method method3 = class_getClassMethod(pClass, @selector(ClassMethodTest)); Method method4 = class_getClassMethod(metaClass, @selector(ClassMethodTest)); NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4); NSLog(@"%s",__func__); } //我们来看打印结果: 2019-12-24 10:36:20.004775+0800 WMTest[1615:59975] 0x100002200-0x0-0x0-0x100002198 2019-12-24 10:36:20.005300+0800 WMTest[1615:59975] testInstanceMethod_classToMetaclass 2019-12-24 10:36:20.005422+0800 WMTest[1615:59975] 0x0-0x0-0x100002198-0x100002198 2019-12-24 10:36:20.005473+0800 WMTest[1615:59975] testClassMethod_classToMetaclass /** 解析: 先看该打印:WMTest[1615:59975] 0x100002200-0x0-0x0-0x100002198 前两个结果是在类和元类中查找其对象方法instanceMethodTest, 在类中,我们得到了对象方法0x100002200,但是在元类中则是0x0, 后两个结果,在类和元类中使用获取对象方法的API获取类方法ClassMethodTest 我们得到的结果是,在类中,没有获取到, 在元类中,我们则通过获取对象方法的API获取到了ClassMethodTest类方法 这里说明:我们类的类方法在元类中是以对象方法存在的! 再看另一个打印结果:WMTest[1615:59975] 0x0-0x0-0x100002198-0x100002198 首先:在类和元类中,通过获取类方法的API去获取对象方法,结果都是0 然后,类和元类中,通过获取类方法的API去获取对类方法,都存在! 但是这里又有了问题,我们上面的结果说明,我们的类方法在元类中是以对象方法存在的,但是这里我们又发现,类方法在元类中,通过获取类方法的API也能得到结果,那这不是矛盾了吗? 我们去看一下获取类方法的API: */ Method class_getClassMethod(Class cls, SEL sel) { if (!cls || !sel) return nil;//先判断cls和sel是否nil //cls->getMeta()获取元类,将元类传入到class_getInstanceMethod中查找对象方法 //原来如此 return class_getInstanceMethod(cls->getMeta(), sel); } Class getMeta() { //是元类,就返回当前元类,不是元类就返回指向元类的ISA. if (isMetaClass()) return (Class)this; else return this->ISA(); }
经过上面的过程,我们看到了类的结构以及属性、成员变量、对象方法、类方法等在类中的存储.再类结构中,我们还看到了一个结构, cache_t cache 缓存,这里面又存了什么呢,下一篇我们去探究!