1.整理的对象和类的c实现:
这是大概结构,具体要比这个复杂一些,具体看文件(准备匆忙随后贴上源码):
2.查看Dad类和Child类用clang生成的c代码:
先解释一下类结构 有 Dad(父类) 和 Child(子类) 两个类, Dad类有 dadName 属性和 _dadIvarName 成员变量;
Child 类有 childName 属性和 _childIvarName 成员变量; 两个类都含有 -(void)testAddress; 和 -(void)printIvars;方法;(准备匆忙,随后贴上类结构图)
先是Dad对象用clang生成的c代码(clang命令在下面):
然后是Child对象:
可以看到即使是继承关系中,每个类都有且只有自己的成员变量,属性和方法列表;子类中并不包含父类的成员变量,属性和方法等;
3.查看通过属性访问和下划线成员变量访问的c代码:
包括:通过clang生成的c代码,其中包括:1.通过setter方法访问父类和子类属性; 2.通过下划线_属性名访问的父类和子类实例变量;
- (void)testAddress {
self.dadName = @"1";
_dadIvarName = @"2";
self.childName = @"3";
_childIvarName = @"4";
}
命令: clang -rewrite-objc Child.m
1.可以看到通过setter方法访问的父类和子类的属性都是通过消息机制发送给self,但是根据OC的消息机制可以知道,如果是父类的方法会通过superclass找到父类,去父类方法列表找到这个方法;这个setter方法是在父类的.m里边生成的,然后通过给_dadname赋值;
2.如果是通过下划线+成员变量名访问的话,可以看到如果是父类的成员变量,根据_dadIvarName生成的代码中含有的的类名Dad,以及_childIvarName中含有的类名Child可以知道,其实编译完成就已经知道属性是属于哪个类;
3.我们可以看到通过成员变量访问时的c代码:
等号的左边是(self + OBJC_IVAR_$_Dad$_dadIvarName) 翻译一下这不就是:
成员变量地址 = (对象地址 + 属于Dad类的_dadIvarName属性)
或者可以理解为: _dadIvarName地址 = 当前对象地址 + Dad类中_dadIvarName属性的偏移量
关于寻找成员变量的偏移量,贴上王晓磊《Objective-C类成员变量深度剖析》中的一段代码:
可以看到其实编译生成的test.ll文件中的LLVM IR代码里显示,其实寻找成员变量偏移量是利用分配成全局变量在编译期间就已经确定,不需要在运行时执行繁琐的寻找过程;
4.查看对象中成员变量的内存(包括从父类继承来的以及当前类自身的),下图是打印父类(上)和子类(下)成员变量的代码:
下图是输出的结果:
根据上图可以看到父类和子类成员变量名以及偏移量,偏移量是从父类第一变量开始到子类最后一个成员变量顺序排布依次增大, 便宜量都依次加8是因为64位下指针是8个字节(我们的 _dadName 等都是字符串), 第一个成员变量便宜量是8是因为对象第一个内容是一个为8个字节的指向类的isa指针;
下面看看对象的首地址以及对象中各成员变量的地址:
以下是打印地址的代码:(子类调父类同名方法)
以及输出的结果:
可以看到,打印的16进制地址从父类到子类根据偏移量依次增长,且符合:
成员变量地址 = 对象(首)地址 + 该成员变量的偏移量;
所以可以得出对象在堆中的地址如右图所示分配:
可以复习一下结构体的性质,结构体是按照其中size最大的变量的size来分配内存(即为一个变量分配一块新空间时最小增加量是size),当前一个指针是8个字节,所以便宜量差值都是8。