随着runtime越来越常用,iOSerruntime的理解要求也越来越高,大家都热衷于runtime源码理解,这篇我带领大家理解下关于Ivar的内容。

1.内存对齐

在分析Ivar之前,我们要了解下内存对齐的概念。 每个特定平台上的编译器都有自己的默认“对齐系数”,而64位中iOS里这个参数是8。我们测试一下:

@interface Dog : NSObject
{int age;         //4个字节BOOL sex;        //1个字节NSString* name;  //8个字节的指针地址short lifeTime;      //2个字节NSString* style;    //8个字节的指针地址
}
@end
复制代码

这是我们新建的类Dog,里面有各种各样的成员变量,如果不存在内存对齐的话,会是一段连续的地址。我们打印下成员变量的地址偏移:

    Class class = objc_getClass("Dog");NSLog(@"内存地址:%p",class);unsigned int count;Ivar* ivars =class_copyIvarList(objc_getClass("Dog"), &count);for (unsigned int i = 0; i < count; i++) {Ivar ivar = ivars[i];ptrdiff_t offset = ivar_getOffset(ivar);NSLog(@"%s = %td",ivar_getName(ivar),offset);}free(ivars);NSLog(@"Dog总字节 = %lu",class_getInstanceSize(objc_getClass("Dog")));
复制代码

运行结果:

2019-03-02 14:26:53.613593+0800 Runtime-Ivar[39894:1319445] 内存地址:0x1060d6f28
2019-03-02 14:26:53.613780+0800 Runtime-Ivar[39894:1319445] age = 8
2019-03-02 14:26:53.613867+0800 Runtime-Ivar[39894:1319445] sex = 12
2019-03-02 14:26:53.613954+0800 Runtime-Ivar[39894:1319445] name = 16
2019-03-02 14:26:53.614038+0800 Runtime-Ivar[39894:1319445] lifeTime = 24
2019-03-02 14:26:53.614123+0800 Runtime-Ivar[39894:1319445] style = 32
2019-03-02 14:26:53.614234+0800 Runtime-Ivar[39894:1319445] Dog总字节 = 40
复制代码

根据打印结果,sex是bool类型,应该只占1个字节,但是却好像占了4个字节,其实这里并不是占了4个字节,而是因为内存对齐,其中3个字节是没用的。我们画下内存结构图:

我们可以看到内存并不是全部占满的,这是由于CPU并不是以字节为单位存取数据的,以单字节为单位会导致效率变差,开销变大,所以 CPU 一般会以 2/4/8/16/32 字节为单位来进行存取操作。而这里,会以8个字节为单位存取。

2.ivar的内存分布

这一部分我们从这4个方面去看ivar的分布情况。

  • 属性与变量的分布
@interface Cat : NSObject
{NSString* c1;NSString* c4;
}@property(nonatomic, copy)NSString* c2;
@property(nonatomic, copy)NSString* c3;
@end
复制代码
unsigned int count;Ivar* ivars =class_copyIvarList(objc_getClass("Cat"), &count);for (unsigned int i = 0; i < count; i++) {Ivar ivar = ivars[i];ptrdiff_t offset = ivar_getOffset(ivar);NSLog(@"%s = %td",ivar_getName(ivar),offset);}free(ivars);NSLog(@"Cat总字节 = %lu",class_getInstanceSize(objc_getClass("Cat")));
复制代码

运行结果:

2019-03-01 17:19:27.926009+0800 Runtime-Ivar[10017:6532014] c1 = 8
2019-03-01 17:19:27.926046+0800 Runtime-Ivar[10017:6532014] c4 = 16
2019-03-01 17:19:27.926056+0800 Runtime-Ivar[10017:6532014] _c2 = 24
2019-03-01 17:19:27.926065+0800 Runtime-Ivar[10017:6532014] _c3 = 32
2019-03-01 17:19:27.926097+0800 Runtime-Ivar[10017:6532014] Cat总字节 = 40
复制代码

我们可以看到先是成员变量后是属性

  • 对象类型与基本类型的分布 先看下属性吧:
@interface Cat : NSObject@property(nonatomic, copy)NSString* c1;
@property(nonatomic, assign)int c3;
@property(nonatomic, copy)NSString* c2;
@property(nonatomic, assign)int c4;
@end
复制代码

打印ivar的方法和上面一致,运行后:

2019-03-01 17:11:58.167160+0800 Runtime-Ivar[9888:6528420] _c3 = 8
2019-03-01 17:11:58.167202+0800 Runtime-Ivar[9888:6528420] _c4 = 12
2019-03-01 17:11:58.167214+0800 Runtime-Ivar[9888:6528420] _c1 = 16
2019-03-01 17:11:58.167224+0800 Runtime-Ivar[9888:6528420] _c2 = 24
2019-03-01 17:11:58.167264+0800 Runtime-Ivar[9888:6528420] Cat总字节 = 32
复制代码

我们可以看到属性的话先是基本类型后是对象类型。 再看下成员变量吧:

@interface Cat : NSObject
{NSString* c1;int c3;NSString* c2;int c4;
}
@end
复制代码

运行结果:

2019-03-01 17:13:05.937474+0800 Runtime-Ivar[9909:6529050] c1 = 8
2019-03-01 17:13:05.937515+0800 Runtime-Ivar[9909:6529050] c3 = 16
2019-03-01 17:13:05.937526+0800 Runtime-Ivar[9909:6529050] c2 = 24
2019-03-01 17:13:05.937534+0800 Runtime-Ivar[9909:6529050] c4 = 32
2019-03-01 17:13:05.937567+0800 Runtime-Ivar[9909:6529050] Cat总字节 = 40
复制代码

我们可以看到成员变量的话没有先后之分。

  • m文件与h文件的分布 先看属性吧:
Cat.h
@interface Cat : NSObject@property(nonatomic, copy)NSString* c1;
@property(nonatomic, copy)NSString* c3;@end
Cat.m
#import "Cat.h"
@interface Cat()
@property(nonatomic, copy)NSString* c2;
@property(nonatomic, copy)NSString* c4;
@end
@implementation Cat
@end
复制代码

运行结果:

2019-03-01 17:16:16.989271+0800 Runtime-Ivar[9962:6530367] _c1 = 8
2019-03-01 17:16:16.989309+0800 Runtime-Ivar[9962:6530367] _c3 = 16
2019-03-01 17:16:16.989319+0800 Runtime-Ivar[9962:6530367] _c2 = 24
2019-03-01 17:16:16.989328+0800 Runtime-Ivar[9962:6530367] _c4 = 32
2019-03-01 17:16:16.989360+0800 Runtime-Ivar[9962:6530367] Cat总字节 = 40
复制代码

我们可以看到先是h文件后是m文件。 再看看成员变量:

Cat.h
@interface Cat : NSObject
{NSString* c1;NSString* c3;
}
@end
Cat.m
#import "Cat.h"
@interface Cat()
{NSString* c2;NSString* c4;
}
@end
@implementation Cat
@end
复制代码

运行结果:

2019-03-01 17:18:05.865890+0800 Runtime-Ivar[9992:6531268] c1 = 8
2019-03-01 17:18:05.865942+0800 Runtime-Ivar[9992:6531268] c3 = 16
2019-03-01 17:18:05.865952+0800 Runtime-Ivar[9992:6531268] c2 = 24
2019-03-01 17:18:05.865960+0800 Runtime-Ivar[9992:6531268] c4 = 32
2019-03-01 17:18:05.866000+0800 Runtime-Ivar[9992:6531268] Cat总字节 = 40
复制代码

和上面一样显示h文件后是m文件。

那我们综合以上几种情况:

Cat.h
@interface Cat : Animal
{NSString* string_h_ivar;int int_h_ivar;}@property(nonatomic, copy)NSString* string_h_property;
@property(nonatomic, assign)int int_h_property;@end
Cat.m
#import "Cat.h"
@interface Cat()
{NSString* string_m_ivar;int int_m_ivar;}
@property(nonatomic, assign)int int_m_property;
@property(nonatomic, copy)NSString* string_m_property;
@end@implementation Cat@end
复制代码

运行结果为:

2019-03-01 16:43:56.295851+0800 Runtime-Ivar[9412:6514675] string_h_ivar = 24
2019-03-01 16:43:56.295907+0800 Runtime-Ivar[9412:6514675] int_h_ivar = 32
2019-03-01 16:43:56.295917+0800 Runtime-Ivar[9412:6514675] string_m_ivar = 40
2019-03-01 16:43:56.295926+0800 Runtime-Ivar[9412:6514675] int_m_ivar = 48
2019-03-01 16:43:56.295934+0800 Runtime-Ivar[9412:6514675] _int_h_property = 52
2019-03-01 16:43:56.295942+0800 Runtime-Ivar[9412:6514675] _int_m_property = 56
2019-03-01 16:43:56.295950+0800 Runtime-Ivar[9412:6514675] _string_h_property = 64
2019-03-01 16:43:56.295960+0800 Runtime-Ivar[9412:6514675] _string_m_property = 72
2019-03-01 16:43:56.296001+0800 Runtime-Ivar[9412:6514675] Cat总字节 = 80
复制代码

分析可得顺序为h文件的ivar->m文件的ivar->h文件的property基本类型->m文件的property对象类型

3.分析ivarlayout源码

runtime.h里面关于IvarLayout的几个方法。

const uint8_t * _Nullable
class_getIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);const uint8_t * _Nullable
class_getWeakIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);void
class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);void
class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

我们试用下,我们创建Person类:

@interface Person : NSObject
{int int1;bool bool1;__strong NSString* strong1;__weak NSString* weak1;char char1;__weak NSString* weak2;__strong NSString* strong2;__strong NSString* strong3;char char2;__weak NSString* weak3;char char3;int  int2;__weak NSString* weak4;__weak NSString* weak5;
}
复制代码

然后我们使用下class_getIvarLayoutclass_getWeakIvarLayout

-(void)getIvarLayout {const uint8_t *strongLayout =   class_getIvarLayout(objc_getClass("Person"));if (!strongLayout) {return;}uint8_t byte;while ((byte = *strongLayout++)) {printf("strongLayout = #%02x\n",byte);}const uint8_t *weakLayout =   class_getWeakIvarLayout(objc_getClass("Person"));if (!weakLayout) {return;}while ((byte = *weakLayout++)) {printf("weakLayout = #%02x\n",byte);}
}
复制代码

打印结果:

strongLayout = #11
strongLayout = #32
weakLayout = #21
weakLayout = #11
weakLayout = #31
weakLayout = #12
复制代码

粗一看看不出什么,文档里面并没有详细说明layout的含义,我们要探究IvarLayout的话,还是要在源码找线索。

void fixupCopiedIvars(id newObject, id oldObject)
{for (Class cls = oldObject->ISA(); cls; cls = cls->superclass) {if (cls->hasAutomaticIvars()) {// Use alignedInstanceStart() because unaligned bytes at the start// of this class's ivars are not represented in the layout bitmap.size_t instanceStart = cls->alignedInstanceStart();const uint8_t *strongLayout = class_getIvarLayout(cls);if (strongLayout) {id *newPtr = (id *)((char*)newObject + instanceStart);unsigned char byte;while ((byte = *strongLayout++)) {unsigned skips = (byte >> 4);unsigned scans = (byte & 0x0F);newPtr += skips;while (scans--) {// ensure strong references are properly retained.id value = *newPtr++;if (value) objc_retain(value);}}}const uint8_t *weakLayout = class_getWeakIvarLayout(cls);// fix up weak references if any.if (weakLayout) {id *newPtr = (id *)((char*)newObject + instanceStart), *oldPtr = (id *)((char*)oldObject + instanceStart);unsigned char byte;while ((byte = *weakLayout++)) {unsigned skips = (byte >> 4);unsigned weaks = (byte & 0x0F);newPtr += skips, oldPtr += skips;while (weaks--) {objc_copyWeak(newPtr, oldPtr);++newPtr, ++oldPtr;}}}}}
}
复制代码

这一段源码是runtime如何使用strongLayoutweakLayout。 下面,我们仔仔细细的分析这段源码,我们取出其中关键的一部分,先看关于strongLayout:

//获得strongLayout的数组,数组元素类型为uint8_t,uint8_t为2位16进制数
const uint8_t *strongLayout = class_getIvarLayout(cls);
if (strongLayout) {//newPtr为ivar的初始地址id *newPtr = (id *)((char*)newObject + instanceStart);unsigned char byte;//遍历strongLayout,并且将内容赋值给bytewhile ((byte = *strongLayout++)) {//取出byte的左边一位unsigned skips = (byte >> 4);//取出byte的右边一位unsigned scans = (byte & 0x0F);//地址跳过skips位newPtr += skips;//循环scans次while (scans--) {// ensure strong references are properly retained.//取出地址里的内容,并且地址+1id value = *newPtr++;if (value) objc_retain(value);}}
}
复制代码

从这一段源码,我们可以看到scans的地址值存放的是strong的成员变量,而skips是无效值,同样我们也可以分析weakLayout的那一段源码。为了能更加清晰的看到ivar的布局,我们通过ivar_getOffset方法获得ivar的内存布局。

-(void)getOffset {unsigned int count;Ivar* ivars =class_copyIvarList(objc_getClass("Person"), &count);for (unsigned int i = 0; i < count; i++) {Ivar ivar = ivars[i];ptrdiff_t offset = ivar_getOffset(ivar);NSLog(@"%s = %td",ivar_getName(ivar),offset);}free(ivars);NSLog(@"Person总字节 = %lu",class_getInstanceSize(objc_getClass("Person")));
}
复制代码

运行结果:

2019-03-04 10:27:23.147813+0800 Runtime-Ivar[32952:841600] int1 = 8
2019-03-04 10:27:23.147842+0800 Runtime-Ivar[32952:841600] bool1 = 12
2019-03-04 10:27:23.147853+0800 Runtime-Ivar[32952:841600] strong1 = 16
2019-03-04 10:27:23.147863+0800 Runtime-Ivar[32952:841600] weak1 = 24
2019-03-04 10:27:23.147875+0800 Runtime-Ivar[32952:841600] char1 = 32
2019-03-04 10:27:23.147884+0800 Runtime-Ivar[32952:841600] weak2 = 40
2019-03-04 10:27:23.147894+0800 Runtime-Ivar[32952:841600] strong2 = 48
2019-03-04 10:27:23.147904+0800 Runtime-Ivar[32952:841600] strong3 = 56
2019-03-04 10:27:23.147913+0800 Runtime-Ivar[32952:841600] char2 = 64
2019-03-04 10:27:23.147922+0800 Runtime-Ivar[32952:841600] weak3 = 72
2019-03-04 10:27:23.147932+0800 Runtime-Ivar[32952:841600] char3 = 80
2019-03-04 10:27:23.147941+0800 Runtime-Ivar[32952:841600] int2 = 84
2019-03-04 10:27:23.147950+0800 Runtime-Ivar[32952:841600] weak4 = 88
2019-03-04 10:27:23.147959+0800 Runtime-Ivar[32952:841600] weak5 = 96
2019-03-04 10:27:23.147992+0800 Runtime-Ivar[32952:841600] Cat总字节 = 104
复制代码

通过这个我们可以画出内存布局图:

这张图可以清晰的看到内存布局,然后我们再把strongLayoutweakLayout加到上面去: 因为无论是strong还是weak都是对象类型的变量,存的都是指针地址,所以占8位。所以源码中的scan地址,其实就是存的strong或者weak的指针地址。我们可以看到在strongLayout中,高位x8代表非strong类型所占的内存地址,低位代表strong类型的个数,在weakLayout中,高位x8代表非weak类型所占的内存地址,低位代表weak类的个数。

runtime之ivar内存布局篇相关推荐

  1. c++对象的内存布局2--进阶篇---C++ 对象的内存布局(上)

    目录(?)[-] 前言 对象的影响因素 知识复习 单一的一般继承 多重继承 前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章留了言,有鼓励 ...

  2. 基类成员的public访问权限在派生类中变为_第17篇:C++继承中虚表的内存布局

    我们已经表明,非虚类的对象实例不包含虚指针,编译器在编译阶段也没有为非虚类没有构建虚表.而本篇我们会从简单的单继承链分析虚类中虚表构造过程和内存布局.这一切假定你有如下基础 对gdb调试器使用有一个比 ...

  3. 走进Java内存布局之JVM入门篇

    内存布局 ​ JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来探讨jV ...

  4. 图文并茂,傻瓜都能看懂的 JVM 内存布局

    本 JVM 系列属于本人学习过程当中总结的一些知识点,目的是想让读者更快地掌握 JVM 相关的知识要点,难免会有所侧重,若想要更加系统更加详细的学习 JVM 知识,还是需要去阅读专业的书籍和文档. 本 ...

  5. 7. 重磅硬核 | 一文聊透对象在JVM中的内存布局,以及内存对齐和压缩指针的原理及应用

    重磅硬核 | 一文聊透对象在JVM中的内存布局,以及内存对齐和压缩指针的原理及应用 大家好,我是bin,又到了每周我们见面的时刻了,我的公众号在1月10号那天发布了第一篇文章?<从内核角度看IO ...

  6. 一文聊透对象在JVM中的内存布局,以及内存对齐和压缩指针的原理及应用

    大家好,我是bin,又到了每周我们见面的时刻了,我在1月10号那天发布了第一篇文章<从内核角度看IO模型的演变>,在这篇文章中我们通过图解的方式以一个C10k的问题为主线,从内核角度详细阐 ...

  7. ? 精美图文带你掌握 JVM 内存布局

    前言 本JVM系列属于本人学习过程当中总结的一些知识点,目的是想让读者更快地掌握JVM相关的知识要点,难免会有所侧重,若想要更加系统更加详细的学习JVM知识,还是需要去阅读专业的书籍和文档. 本文主题 ...

  8. 图文并茂:JVM内存布局彻底懂了!

    文章来源:https://juejin.cn/post/6844904033396719624 前言 本JVM系列属于本人学习过程当中总结的一些知识点,目的是想让读者更快地掌握JVM相关的知识要点,难 ...

  9. C# CLR 聊聊对象的内存布局 一个空对象占用多少内存

    在 C# 中的对象大概可以分为三个不同的类型,包括值类型.引用类型和其他类型.本文主要讨论的是引用类型对内存空间的占用情况.在讨论开始之前我想问问大家,一个空的对象会占用多少内存空间?当然这个问题本身 ...

  10. 【C++】C++对象模型:对象内存布局详解(C#实例)

    C++对象模型:对象内存布局详解 0.前言 C++对象的内存布局.虚表指针.虚基类指针解的探讨,参考. 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个概念可 ...

最新文章

  1. windows环境下,django + mongoengine + mongodb环境配置
  2. hdu 5592 ZYB's Premutation (线段树+二分查找)
  3. HDU - 5371 Hotaru's problem(马拉车+暴力)
  4. 对眼睛有利的屏幕颜色
  5. mysql企业版安装_mysql企业版怎么安装图解
  6. wget下载太慢问题
  7. 445端口不通经验总结
  8. flash 模拟eeprom
  9. Python类型说明符、格式限定符(格式说明符)
  10. Matplotlib系列(一):快速绘图入门
  11. 一年前,我来国企搞IT
  12. 移动硬盘突然识别不了!
  13. 安卓10.1寸大屏车载导航
  14. Matlab-图片上画线
  15. 端口扫描程序设计c语言,主机端口扫描程序设计.doc
  16. python:matplotlib.pyplot绘制散点图(基础一)
  17. 身体指数bmi流程图_【新城校区】新生体检丨你了解你的身体吗?
  18. 【机器学习入门——1】Python 开发环境的安装 Python(x,y)及Pycharm
  19. 中国珠算术语与计算机术语,新计算技术珠算与珠心算 王家申等编著.pdf
  20. python hydra库

热门文章

  1. 怀才当遇网—毕业季 | 你总说毕业遥遥无期,转眼就各奔东西
  2. 网页布局:左边为导航,右边正文,左边和右边的高度总是相等,或者导航最低高度为屏幕高度...
  3. iOS-自定义导航栏后侧滑返回功能失效
  4. ONVIF流媒体播放流程
  5. CEGUI中文显示四步曲
  6. IOCP实现聊天服务
  7. Redis protected-mode属性解读
  8. Python基础模块
  9. WinForm------GridControl添加底部合计框
  10. 2011-8-4 今天完成了去掉上传文件(input type=file)的框框 哎 人家客户不要框框。...