内存管理

内存管理

内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其主要目的是如何高效、快速的分配。

大纲

  1. 堆和栈
  2. 引用计数
  • MRC
    深浅拷贝
    autorelease
  • ARC
    所有权修饰符
    常见的几种内存泄漏
    使用instruments检测内存泄漏

堆和栈

一种特别的树状的数据结构。
在队列中,调度程序反复提取队列中的第一个作业并运行,因为实际情况中某些较短的任务要等待很长时间才能结束,或某些不短小,但具有重要性的作业,同样具有优先权。堆即为解决此类问题设计的一种数据结构。(按照元素的优先级取出元素)

一种特殊的串列形式的抽象数据结构,其特殊之处在于只能允许在链表或数组的一端进行加入数据或输出数据的运算。
后进先出LIFO。

内存分配中的堆和栈

内存分布结构.png

堆栈空间分配

栈,是一块连续的内存区域,从高地址往低地址扩展。由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆,是一块不连续的内存区域,从低地址往低高址扩展。一般由程序员释放,若程序员不释放,程序结束后可能由OS释放,分配方式类似于链表。
堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配,动态分配是alloca函数进行分配。栈的动态分配和堆的动态分配不同,是由编译器释放的。

堆栈缓存方式

栈,一级缓存,被调用时处于存储空间中,调用完毕立即释放。
堆,二级缓存,生命周期由虚拟机的垃圾回收算法来决定。调用速度相当对慢

引用计数

概念

一种内存管理技术,将资源的被引用计数保存起来,当引用计数变为零时就将其释放的过程。

MRC(Manual Reference Counting)

自己生成的对象,自己持有。(使用alloc,new,copy,mutablecopy或以这些单词开头的方法)
非自己生成的对象自己也能持有。(retain,非自己生成的对象内部一般是用的autorelease实现的,可以做到本身不持有对象例如:[NSArray array]操作)
不再需要自己持有的对象时释放。(release)
非自己持有的对象不能释放。(倘若在程序中释放了非自己所持有的对象就会造成崩溃)

深浅拷贝

copy,mutablecopy。一般系统自带的类实现了NSCopying和NSMutableCopying协议,自定义的类需要自己实现NSCopying和NSMutableCopying协议。

  • 浅拷贝 指针拷贝,内存地址相同
  • 深拷贝 值拷贝,内存地址不同
NSMutableArray NSArray NSString NSMutableString
copy 深拷贝 浅拷贝 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝 深拷贝 深拷贝

总结:可变对象不论执行copy操作还是mutablecopy操作,都是深拷贝,不可变对象执行mutablecopy操作是深拷贝。深拷贝的数组里的元素是浅拷贝(内存地址相同)

autorelease

当对一个对象发送autorelease消息,会将对象添加到NSAutoreleasePool中去,当自动释放池执行drain操作时,会自动给里面的对象发送release消息,来释放对象。

ARC(Automatic Reference Counting)

编译器在编译时会帮我们自动插入
retain,release,copy,autorelease,autoreleasepool。
-fno-objc-arc,可以指定某个文件不使用ARC.

autoreleasepool

autorelease对象到底是何时被析构的?
一个常见的误区就是代码块执行完毕会释放,但其实并不是这样的。autoreleasepool的drain操作是跟runloop有关的,一个runloop结束以后才会执行drain操作,所以在代码块执行完毕后autorelease对象不一定会会析构。

for (int i =0;i<1000000;i++){
NSString *str = [NSString stringWithString:@"hahahha"];
}
for (int i =0;i<1000000;i++){
    @autoreleasepool{
    NSString *str = [NSString stringWithString:@"hahahha"];
    }
}
  • 结论:
    第二段代码会对内存进行优化,释放速度块
    第一段代码造成内存大量堆积,释放速度慢
    使用场景:当程序有大量中间临时变量产生时,避免内存峰值过高,及时释放内存的场景

所有权修饰符

  • __strong

__strong修饰符表示对象的“强引用”,是id类型和对象类型默认的所有权修饰符。__strong修饰的变量在超出其作用域时,会释放其被赋予的对象。

  • __weak
    __weak弱引用,不能持有对象实例,当该对象被废弃时,此弱引用会自动失效且处于nil赋值的状态。
  • __unsafe_unretained
    __unsafe_unretained是不安全的修饰符,有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,不持有对象。
  • __autoreleasing
    不用显示的附加__autoreleasing修饰符,这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool.
属性与所有权修饰符的对应关系
属性的修饰符 所有权修饰符
assign __unsafe_unretained
copy __strong
retain __strong
strong __strong
unsafe_unretained __unsafe_unretained
weak __weak

给属性赋值是就相当于赋值给附加各属性对应的所有权修饰符的变量中。只有copy不是简单的赋值操作,是通过NSCoping接口中的copyWithZone:方法赋值源所生成的对象。

常见的几种内存泄漏

内存泄漏就是当废弃的对象在超出其生命周期后继续存在。

  1. 对象类型变量作为C语言结构体成员
struct Data{
NSMutableArray __unsafe_unretained *array
}

注意,这里要加上__unsafe_unretained修饰符,否则会报错,对象类型的变量不能直接作为结构体的成员变量。(可以用__bridge和c语言变量进行值转换)

  1. 循环引用
  • 两个对象相互持有对方的强引用,可以用弱引用(weak)解决。
  • block持有self对象,需要在block块外面设置弱引用,里面设置强引用解决。
__weak __typeof(self) weakSelf = self;
self.block = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething];
};

这里block里面用强引用,为了保证在block里面访问self时能保证self不被释放。

  • NSTimer
    示例代码
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(onTimerTimeOut) userInfo:nil repeats:YES];
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
image.png

当前类被timer强引用,dealloc方法并不会执行。
有一种解决办法是像blockskit一样,用block实现,解除循环引用(不过也需要注意block的循环引用)

+ (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
    NSParameterAssert(block != nil);
    return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

还有一种方法,利用中间类,来破除循环引用,这里就不多说啦。

使用instruments检测内存泄漏

有几种方式打卡instruments调试工具
1.Xcode->Open Develop Tool->Instruments
2.Product->Profile
3.长按运行按钮->Profile

一. 使用Leaks检测

  • 强引用
    无循环引用:


    15483201379515.jpg

    有循环引用:


    15483175101215.jpg
  • block循环引用
    无循环引用:


    15483202064026.jpg

有循环引用:


15483176662501.jpg
  • NSTimer
    无循环引用:


    15483204816830.jpg

    有循环引用:


    15483177310618.jpg

通过上面的图可以分析出来,使用Leaks只能检测出block的循环引用(红色x标记)。但其实以上几种情况发生循环引用时的内存都是只升不减的。

具体的查看方法
15483210740766.jpg

选中红色✘这个范围,在下方选择Call Tree,并勾选invert call tree和Hide system Library就可以看到具体发生内存泄漏调用的函数,双击则会定位到具体的代码。如下图:


15483212847970.jpg

Leaks还可以选择Cycles&Roots查看发生循环引用的地方


15483215773045.jpg

二.使用Allocation检测

15483218051451.jpg

对AViewController不断地进行push、pop操作,用Allocations可以查看到AViewController的Totol数不为0,然后就可以针对这个类的使用情况再深入排查可能会出现内存泄漏的地方
15483219394069.jpg

总结:虽然使用ARC后能够大大避免内存泄漏的出现,但还是有一些场景会导致内存泄漏,上述例子的场景还是比较容易避免的,在调用链长的时候一些循环引用就比较难发现了,因此我们在开发完一个功能模块后使用Instruments来检测一遍是比较好的习惯。
最后附上Demo
TestInstruments

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 作者:limboy文章源自:http://limboy.me/ios/2016/03/10/mgj-compone...
    IT程序狮阅读 12,873评论 5 39
  • 在组件化之前,蘑菇街 App 的代码都是在一个工程里开发的,在人比较少,业务发展不是很快的时候,这样是比较合适的,...
    yuditxj阅读 547评论 0 1
  • 原文地址:蘑菇街 App 的组件化之路 在组件化之前,蘑菇街 App 的代码都是在一个工程里开发的,在人比较少,业...
    prettystony阅读 868评论 0 1
  • 为什么会有这篇文章呢? 和之前的同事"我是你爸爸"讨论了关于组件化的事,对我有很大的启发。在此特别感谢"我是你爸爸...
    曹俊_413f阅读 1,841评论 0 14
  • Android组件化之路 首先先分清楚两个概念: 模块化 模块化编程是将一个程序按照功能拆分成相互独立的若干模块,...
    WuRichard阅读 7,368评论 0 11