alloc、init你弄懂50%了吗?
前言
这是一篇我记录对alloc、init分析思考的笔记。如果读者想看懂我的第二个思考,可能需要您至少了解内存的分段分页管理,如果您对其一点都不知道,可以先看这篇软文简单了解一下。另外很重要的一点是,请先思考。
思考1.对象为什么要alloc,init又是干嘛的?
很多人都知道,初始化一个对象应该这么写:
MyClass* myObj = [MyClass alloc] init];
那么有没有思考过为什么呢?其实我这么写也是完全可以的:
MyClass *myObj = [MyClass alloc];
myObj = [myObj init];
我们来看看这干了啥。
alloc allocates a chunk of memory to hold the object, and returns the pointer.
就是说alloc分配了一坨 内存给对象,让它不释放,并且把地址返回给指针。
MyClass *myObj = [MyClass alloc];
那么这样过后myobj为什么不能被使用呢?这是因为这片内存还没有被正确的初始化。
举个栗子,万达要修房子,他们第一步一定是要先向政府搞到一块地,第二步才能在这块地上动工修楼。
这里操作系统就是政府,alloc就是去争地,init就是在地上修房子。没有调用init,房子都没有修好,别人怎么买房进去住?所以我们需要用init来初始化这片内存:
-init{
self=[super init]; // 1.
if(self){ // 2.
....
}
return self; // 3.
}
第一步需要初始化父类的信息,比如实例变量等等。可以理解成王思聪在修房子前要询问他老爸的意见,他老爸说想娱乐会所,他没有意见的话就会修成娱乐会所,他如果有意见,就可以悄悄的在第二步里面改为修成LOL俱乐部。第三步就不说了。
最后提醒一下,不要这样写:
MyClass* myObj = [MyClass alloc];
myObj=[myObj init];
因为你可能会忘记在第二行加init,并且代码也会增长。
思考2.关于alloc的思考
在思考1中我们说了:alloc分配了一坨 内存给对象,让它不释放,并且把地址返回给指针。这里主要有两个问题:
调用alloc后内存是直接映射到堆还是只分配给了虚拟内存?
这一坨内存到底是多大?
我们依次来展开。
可能有些读者不明白第一个问题是什么意思,这里需要额外讲一些关于内存的东西,其实这是iOS开发很重要的东西,不管是面试还是学习都可能会用到。
额外的东西
iOS里的内存是有分类的,它分成Clean Memory和Dirty Memory。顾名思义,Clean Memory是可以被操作系统回收的,Dirty Memory是不可被操作系统回收的。
Clean Memory:在闪存中有备份,能再次读取重建。如:
Code(代码段),framework,memory-mapped files
Dirty Memory:所有非Clean Memory,如:
被分配了的堆空间,image cache
举个栗子,在这样的代码中:
- (void)dirtyOrCleanMemory
{
NSString *str1 = [NSString stringWithString:@"Welcome!"]; // 1.
NSString *str2 = @"Welcome"; // 2.
char *buf = malloc(100 * 1024 * 1024); // 3.分配100M内存给buf
for (int i = 0; i < 3 * 1024 * 1024; ++i) {
buf[i] = rand();
} // 4.buf的前3M内存被赋值
}
对每行分析:
1.Dirty Memory。
因为stringWithString:是在堆上分配内存的,如果我们不回收它的话,系统会一直占用这块内存。
2.Clean Memory。
因为用这样的方法创建的是一个常量字符串,常量字符串是放在只读数据段的,如果这块内存被释放了,而我们又访问它的时候,操作系统可以在只读数据段中把值再读取出来重建这块内存。(ps:所以用这种方法创建的string是没有引用计数的。)
接下来的知识就是引出思考问题1、2比较重要的点了:
3.Clean Memory。
这个时候buf指向的100M内存区域是Clean Memory的,因为操作系统是很懒的,只有当我们要用到这块区域的时候才会映射到物理内存,没有使用的时候只会分配一块虚拟内存给buf。读起来很绕口,上张图:
可以看到虚拟内存和物理内存没有映射关系,所以是Clean Memory的。
4.Dirty & Clean Memory混合。
前3M是Dirty Memory,后97M是Clean Memory。这句for语句执行完成后,buf的前3M内存被赋值,也就是buf的前3M被使用了,所以这个时候的映射关系是这样的:
额外的东西Done.
回到主线
调用alloc后内存是直接映射到堆(物理内存)还是只分配给了虚拟内存?
一坨内存的一坨是多大?
这个时候我们的第一个问题读者应该能明白了。那么我们怎么验证alloc是直接映射到堆上还是只分配给虚拟内存呢?这个问题让我想了好些天,最后xo哥想到了一剂良药来验证,那就是用instrument来推反。
使用instrument来证反
我们假设的论点是:对象收到alloc消息后只在虚拟内存分配空间。
这里需要一丁点代码。
1.我们随便新建个工程。
2.然后做个model类:
#import <Foundation/Foundation.h>
@interface XOModel : NSObject
{
int a1;
NSString *a2;
}
@end
3.在controller里给view加一个点击事件:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (int i = 0; i < 100000; ++i) {
XOModel *model = [XOModel alloc]; // 注意这句只有alloc
[self.array addObject:model];
}
}
4.打开instrument的alloction,运行触发一下点击事件,查看如下:
(图解:Persistent bytes表示所有的这类东西在堆里的大小,Persistent表示所有的这类东西的个数.)
我们发现发现在Persistent bytes(堆)里实实在在地分配给了XOModel 3.05MB的空间。
我们再触发一下点击事件:
发现堆分配给了XOModel的大小空间变成了原来的两倍6.10MB。
结论过渡:如果对象收到alloc消息只在虚拟内存分配空间,那么persistent bytes(堆)里是不会分配给XOModel大小的,也就是说这里的persistent bytes大小应该是0。所以问题1的结论如下:
结论:alloc不只分配在虚拟内存,同时会在物理内存建立映射。
对象的内存分配
最后剩下我们的最后一个问题:类对象收到alloc消息后,操作系统会分配出来的一坨内存是多大?
3.05M的大小是100000个XOModel对象的总和,那么一个XOModel的实例对象,操作系统会给他分配多大的空间呢?很简单嘛,3.05M/100000就得到了,等等,难道你真准备这样去算?好吧,其实一开始我真这样想过,但是这肯定是算不出准确答案的,关键是你要去思考。
这里有两种办法,我采用的第二种办法。
第一种验证方法还是instrument
我们可以修改一下触发的代码,然后重新刷新instrument查看XOModel大小,具体操作同上,不重复了:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (int i = 0; i < 1; ++i) { // 修改处
XOModel *model = [XOModel alloc]; // 注意这句只有alloc
[self.array addObject:model];
}
}
第二种验证办法借用runtime
我们可以借助runtime来查看一个类对象所需要的内存大小。值得一提的是我最开始用的方法是class_getInstanceSize,原型如下:
/**
* Returns the size of instances of a class.
*
* @param cls A class object.
*
* @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
*/
OBJC_EXPORT size_t class_getInstanceSize(Class cls)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
貌似是我们需要的函数,但是我发现这个方法有bug,返回的size和instruments的值不同,后来又发现有人遇到同样的问题,所以摘抄了另一种方法,代码如下:
#import <objc/runtime.h>
#import <malloc/malloc.h>
...
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
XOModel *model = [XOModel alloc];
[self.array addObject:model];
NSLog(@"Size of %<a href="http://www.jobbole.com/members/q697158886">@:</a> %zd", NSStringFromClass([XOModel class]), malloc_size((__bridge const void *) model));
}
再贴下XOModel代码:
#import <Foundation/Foundation.h>
@interface XOModel : NSObject
{
int a1;
NSString *a2;
}
@end
在iPhone 6(或其他64位)的机子上运行,输出如下:
AllocTest[38470:2551068] Size of XOModel: 32
“啊咧,我一个int,一个指针你分给我32个字节,操作系统你是脑子进屎了吗?”
我们再修改一下XOModel代码,不要实例变量:
#import <Foundation/Foundation.h>
@interface XOModel : NSObject
{
}
@end
输出如下:
AllocTest[38630:2562602] Size of XOModel: 16
“我靠,我什么东西都没有,操作系统你还要分给我16个字节,是不是傻?”
智慧的操作系统这样做当然是有它自己的原因滴,这里我们需要知道三个东西:
任何类对象都有一个isa指针,需要分配内存。
32位机子上指针大小为4字节,64位机子为8字节。
字节对齐。
第一点就不说了,不知道的话您多半也没耐心看到现在了。
第二点贴个文档图,iOS7过后部分苹果机就开始从32位操作系统转到64位了,所以部分数据类型的大小也有变化,这里我们主要关注例子中的指针:
现在,基于这两点对样例分析。
第一个样例(类对象中一个int,一个NSString指针,一个isa指针),64位操作系统上应该是4+8+8=20,然而输出是32,不对。
第二个样例(类对象中只有一个isa指针),64位操作系统上应该是8,然而输出是16,还是不对。why?
字节对齐
字节对齐我了解得也不是太多,简单点讲目的就是为了提高存取效率,概念就不展开了,可以看这个,我这里就直接讲原理了。先贴一份苹果的文档:
When allocating any small blocks of memory, remember that the granularity for blocks allocated by the malloc library is 16 bytes. Thus, the smallest block of memory you can allocate is 16 bytes and any blocks larger than that are a multiple of 16. For example, if you call malloc and ask for 4 bytes, it returns a block whose size is 16 bytes; if you request 24 bytes, it returns a block whose size is 32 bytes. Because of this granularity, you should design your data structures carefully and try to make them multiples of 16 bytes whenever possible.
有点长,简单的意思就是:
当我们分配一块内存的时候,假设需要的内存小于16个字节,操作系统会直接分配16个字节;加入需要的内存大于16个字节,操作系统会分配a*16个字节。举个栗子,如果你调用malloc并且需要4个字节,系统会给你一块16个字节的内存块;如果你调用malloc并且需要24个字节,系统会给你一块32个字节的内存块。
现在再看我们的栗子,就可以直接上图了:
第一个例子不对齐应该是20字节,对齐就是32字节。
第二个例子不对其应该是8字节,对齐就是16字节:
ps:在32位机器上可能会有不一样的结果,因为指针大小不同,但是32位的苹果机也是16字节对齐的。
至此我们对alloc的探究就结束了。
结语
这次的探究算是比较彻底了,过程当中也学到了很多东西,在这个浮躁的社会,学贵在道,术次之,打好基础、保持思考才不会磨灭掉你对它最初的兴趣。
参考链接:(由于微信不允许发链接,可点击原文进行查看)
iOS内存管理及优化-腾讯庄延军
Checking the size of an object in Objective-C – Stack Overflow
Does class_getInstanceSize have a known bug about returning incorrect sizes? – Stack Overflow
Memory Usage Performance Guidelines – 苹果文档
字节对齐-百度百科
alloc、init你弄懂50%了吗?的更多相关文章
- 彻底弄懂 Unicode 编码
彻底弄懂 Unicode 编码 今天,在学习 Node.js 中的 Buffer 对象时,注意到它的 alloc 和 from 方法会默认用 UTF-8 编码,在数组中每位对应 1 字节的十六进制数. ...
- 30分钟彻底弄懂flex布局
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由elson发表于云+社区专栏 目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布 ...
- 【TensorFlow】一文弄懂CNN中的padding参数
在深度学习的图像识别领域中,我们经常使用卷积神经网络CNN来对图像进行特征提取,当我们使用TensorFlow搭建自己的CNN时,一般会使用TensorFlow中的卷积函数和池化函数来对图像进行卷积和 ...
- 一文弄懂神经网络中的反向传播法——BackPropagation【转】
本文转载自:https://www.cnblogs.com/charlotte77/p/5629865.html 一文弄懂神经网络中的反向传播法——BackPropagation 最近在看深度学习 ...
- 弄懂flex布局
目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布局以及它的好处,这里就不再赘述. 在这篇文章里,想说说flex布局的属性语法及其细节.那么网上也 ...
- iOS开发new与alloc/init的区别
[className new]基本等同于[[className alloc] init]: 区别只在于alloc分配内存的时候使用了zone. 这个zone是个什么东东呢? 它是给对象分配内存的时候, ...
- new和alloc init的区别
背景说明,new是较为老式的写法,后来发现只有一个new不好使,才引入了alloc和init这种写 法,保留new一是向后兼容,二是很多时候是一种更简单的写法.其实是一样的,new在内部调用 的all ...
- 彻底弄懂LSH之simHash算法
马克·吐温曾经说过,所谓经典小说,就是指很多人希望读过,但很少人真正花时间去读的小说.这种说法同样适用于“经典”的计算机书籍. 最近一直在看LSH,不过由于matlab基础比较差,一直没搞懂.最近看的 ...
- 必须弄懂的495个C语言问题
1.1 我如何决定使用那种整数类型? 如果需要大数 值(大于32, 767 或小于¡32, 767), 使用long 型.否则, 如果空间很重要(如有大数组或很多结构), 使用short 型.除此之外 ...
随机推荐
- c# 中List<T> union 深入理解
http://www.cnblogs.com/qinpengming/archive/2012/12/03/2800202.html 借用 这个兄弟的代码 我就不献丑了 .我这里指记录下 public ...
- Bridage
对于有两个以上的维度的对象,如下图:这张图的业务逻辑是这样的,Hayes,USR以及Emie都是上网的猫,现在有两条线路,一条是传统Dial,还有一条线路是专线,不需要拨号,这样每创建一种线路就意味着 ...
- jdk各个版本
http://www.cnblogs.com/langtianya/p/3757993.html
- 卷积神经网络 cnnff.m程序 中的前向传播算法 数据 分步解析
最近在学习卷积神经网络,哎,真的是一头雾水!最后决定从阅读CNN程序下手! 程序来源于GitHub的DeepLearnToolbox 由于确实缺乏理论基础,所以,先从程序的数据流入手,虽然对高手来讲, ...
- Uva_11021 Tribles
题目链接 题意: 现在有k只麻球, 每只麻球只能存活一天, 第二天就会死去, 死去之前可能生下x只小麻球(x = 0,1,2,...,n 1), 概率分别为P[0], P[1], ... , P[n ...
- bzoj 1501: [NOI2005]智慧珠游戏 Dancing Link
1501: [NOI2005]智慧珠游戏 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 190 Solved: 122[Submit][Status] ...
- BZOJ 1831 逆序对
Description 小可可和小卡卡想到Y岛上旅游,但是他们不知道Y岛有多远.好在,他们找到一本古老的书,上面是这样说的: 下面是N个正整数,每个都在\(1 \sim K\)之间.如果有两个数\(A ...
- 百部BBC经典纪录片,附地址,需要的请抱走
- app内购提示,您已购买此商品,但未下载
出现这样的问题,是支付没有finish造成的,一般在支付过程中断网了,下次再购买同一商品的时候就会出现这样的问题, 解决办法,在点击购买的时候判断支付队列中是否有为finish的商品,若有,则进行处理 ...
- c++调用ffmpeg
在自己编译好ffmpeg库后,已经迫不及待的想尝试用vs2010来调用ffmpeg,在开始调用的时候遇到了些问题,但还是解决了. 配置vs 1.右键工程-属性,在然后选择 配置属性 -> C/C ...