半自动管理内存,MRC
内存管理:如何正确释放堆上的空间
内存压根儿就没释放---------内存泄露
在使用之前,内存被释放了------提前释放
释放过后又释放内存---------重复释放
非自动管理内存的使用原则:
alloc retain new以copy开头的方法以mutableCopy开头的方法,都要相应的使用release autorelease
自己收拾的自己工作
//retainCount专门用来计数引用计数
//retain,copy,new,mutableCopy给计数器加1 (方法)
//release给计数器减1(方法) 当retainCount=0,内存释放
int main()
{
Car *car = [[Car alloc] init];
Person *p1 = [[Person alloc] init];
[p1 setCar:[car retain]];
NSLog(@"%ld",[[p1 car] retainCount]);
Person *p2 = [[Person alloc] init];
[p2 setCar:[car retain]];
NSLog(@"%ld",[[p2 car] retainCount]);
[[p2 car] release];
NSLog(@"car = %ld",[car retainCount]);
[p2 release];
[[p1 car] release];
NSLog(@"car = %ld",[car retainCount]);
[p1 release];
[car release];
}
init property setter delloc的内存管理
init property
setter dealloc
property属性访问声明
readwrite,readonly,assign,retain,copy,nonatomic属性的作用
@property是一个属性访问声明,扩号内支持以下几个属性:
1,getter=getterName,setter=setterName,设置setter与getter的方法名
2,readwrite,readonly,设置可供访问级别
2,assign,setter方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题
3,retain,setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序(CC上有相关资料)
4,copy,setter方法进行Copy操作,与retain处理流程一样,先旧值release,再Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。
copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。
5,nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级(我是这么理解的...)。
atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错 误的结果。加了atomic,setter函数会变成下面这样:
当你定义了一系列的变量时,需要写很多的getter和setter方法,而且它们的形式都是差不多的,,所以Xcode提供了@property和@synthesize属性,@property用在 .h 头文件中用作声明,@synthesize用在.m 文件中用于实现。
如下,新建一个基于“Command Line Tool”的项目,名为“property”,再新建一个Student类,
传统的写法是:
Student.h
[cpp] view plaincopyprint?
//
// Student.h
// property
//
// Created by Rio.King on 13-8-25.
// Copyright (c) 2013年 Rio.King. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Student : NSObject
{
int age;
int no;
}
//age的getter和setter方法声明
- (int)age;
- (void)setAge:(int)newAge;
//no的getter和setter方法声明
- (int)no;
- (void)setNo:(int)newNo;
@end
Student.m
[cpp] view plaincopyprint?
//
// Student.m
// property
//
// Created by Rio.King on 13-8-25.
// Copyright (c) 2013年 Rio.King. All rights reserved.
//
#import "Student.h"
@implementation Student
//age的getter和setter方法的实现
- (int)age
{
return age;
}
-(void)setAge:(int)newAge
{
age = newAge;
}
//no的getter和setter方法的实现
- (int)no
{
return no;
}
- (void)setNo:(int)newNo
{
no = newNo;
}
@end
main.m
[cpp] view plaincopyprint?
//
// main.m
// property
//
// Created by Rio.King on 13-8-25.
// Copyright (c) 2013年 Rio.King. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
// insert code here...
Student *stu = [[Student alloc] init];
stu.age = 100;//这句相当于setter方法
NSLog(@"age is %i", stu.age);//这里的 stu.age 相当于getter方法
[stu release];
}
return 0;
}
------------------------------------------------------------------------------------------------------------------------
用@property和@synthesize的写法是:
Student.h
[cpp] view plaincopyprint?
//
// Student.h
// property
//
// Created by Rio.King on 13-8-25.
// Copyright (c) 2013年 Rio.King. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Student : NSObject
{
int age;
int no;
}
//当编译器遇到@property时,会自动展开成getter和setter的声明
@property int age;
@property int no;
@end
Student.m
[cpp] view plaincopyprint?
//
// Student.m
// property
//
// Created by Rio.King on 13-8-25.
// Copyright (c) 2013年 Rio.King. All rights reserved.
//
#import "Student.h"
@implementation Student
//@synthesize 会自动生成getter和setter的实现
//@synthesize 默认会去访问age,no,height同名的变量,,
//如果找不到同名的变量,会在内部自动生成一个私有同名变量age,no,height,,
//因此Student.h 中的这几个变量也可以省略不写。
@synthesize age,no;
@end
main.m
[cpp] view plaincopyprint?
//
// main.m
// property
//
// Created by Rio.King on 13-8-25.
// Copyright (c) 2013年 Rio.King. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
// insert code here...
Student *stu = [[Student alloc] init];
stu.age = 100;
NSLog(@"age is %i", stu.age);
[stu release];
}
return 0;
}
几点说明:
1.在Xcode4.5及以后的版本中,可以省略@synthesize ,编译器会自动帮你加上getter 和 setter 方法的实现,并且默认会去访问
_age这个成员变量,如果找不到_age这个成员变量,会自动生成一个叫做 _age的私有成员变量。
2.建议变量名用"_"前缀作为开头,但我看big Nerd 那本书里是不用的,个人也比较习惯 big Nerd 的那种写法,所以变量名就不加前缀了。Y^o^Y
自动释放池是什么,如何工作
当您向一个对象发送一个autorelease消息时,Cocoa就会将该对象的一个引用放入到最新的自动释放池。它仍然是个正当的对象,因此自动释放池定义的作用域内的其它对象可以向它发送消息。当程序执行到作用域结束的位置时,自动释放池就会被释放,池中的所有对象也就被释放。
1. ojc-c是通过一种"referring counting"(引用计数)的方式来管理内存的, 对象在开始分配内存(alloc)的时候引用计数为一,以后每当碰到有copy,retain的时候引用计数都会加一, 每当碰到release和autorelease的时候引用计数就会减一,如果此对象的计数变为了0, 就会被系统销毁.
2. NSAutoreleasePool 就是用来做引用计数的管理工作的,这个东西一般不用你管的.
3. autorelease和release没什么区别,只是引用计数减一的时机不同而已,autorelease会在对象的使用真正结束的时候才做引用计数减一.
新关键字strong,__strong, weak, __weak(“__”为双下划线,(unsafe_unretained,__unsafe_unretained为iOS4.0不支持weak而使用,等同于assign,目前可以不考虑)strong 强引用等同于非ARC的retain
@property (nonatomic, retain) 改为使用@property (nonatomic, strong)
成员变量默认也为strong属性,可写作__strong NSString *str; 或省略直接写为NSString *str;
方法中的strong变量,在方法后会自动释放,类的strong成员变量在类释放时会自动释放。
weak 弱引用
与assign类似,但不全相同。当对象释放时会自动设置为nil而不需显式的设置。
如:
__weak NSString *testStr;- (void)test
{
NSString *abc = [NSString stringWithFormat:@"abc"];testStr = abc;
abc = [NSString stringWithFormat:@"def"];
// testStr会自动等于nil, 即使没有立刻变为nil,在下一个程序循环中也会变为nil,这点与assign不同,assign仍然会有指向空地址的指针,需要手动设为nil,否则再使用就造成crash
}
delegate及xib的outlet 的属性应该设置为@property @property (nonatomic, weak)
copy和之前的copy一样,复制一个对象并创建strong关联
比如:
NSTimerNSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:selfselector:@selector(timerAction) userInfo:nil repeats:YES];
timer的repeat会对self有引用,而self也对timer有引用,即使页面返回了,self及timer也不会释放。
解决方法是,在页面返回前停止timer [timer_ invalidate],可视实际情况放在viewWillDisapper里执行。(放在dealloc没用,页面没有释放不会执行dealloc)
延时执行
[self performSelector:@selector(delayFunc) withObject:nilafterDelay:5];
即使页面返回,也必须等此延时方法执行后才会释放。因此需要在页面返回前执行
[NSObject cancelPreviousPerformRequestsWithTarget:self];
(当然这里指的是一般情况,必须要此方法执行的话可以不用写这句,特殊情况特殊处理:)
不过 ARC 只能作用于 Objective-C 对象,不能释放 Core Foundation对象。 因此这里你仍然需要调用 CFRelease()来释放该对象。
ARC下__strong和__weak的区别
先用Xcode新建一个命令行工程
新建一个Person类
在.m文件中重写dealloc函数
- (void)dealloc
{
NSLog(@"Person is dealloc");
}
先了解ARC的基本原理
/*
ARC的判断准则:只要没有强指针指向对象,就会释放对象
指针分2种:
1>强指针:默认情况下,所有指针都是强指针__strong
2>弱指针: __weak
*/
打开main函数
第一个测试
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Person *p = [[Person alloc]init]; //有一个强指针指向Perosn对象
//在这里 把p为空
p = nil; //这行代码过后 清空指针对象没有强指针指向 对象会被释放
/*会打印*/
/* Person is dealloc */
NSLog(@"-------");
}
return 0;
}
第二个测试
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Person *p = [[Person alloc]init]; //有一个强指针指向Perosn对象
Person *p2 = p; //p2也指向Person对象 就有两个强指针指向
p = [[Person alloc]init];//创建一个新对象让p来指向 但另外一个Person对象仍有强指针指向
NSLog(@"-------");
}
//所以对象会在程序结束后释放
return 0;
}
下面看弱指针的测试
int main(int argc, const char * argv[])
{
//接下来看若指针
// __strong 声明强指针
// __weak 弱指针
Person *p = [[Person alloc]init];
__weak Person *p2 = p;//声明一个弱指针 指向对象
p = nil; //把强指针指向变为空 对象没有强指针指向 会被释放
/*
只要强指针指向的对象不存在 ARC会自动给指向该对象的弱指针清空 这样就会防止野指针错误
*/
p2 = nil; //若指针不能决定对象的释放
NSLog(@"----------");
return 0;
}
关于ARC使用总结
ARC下的dealloc
众所周知,iOS开发的时候,使用ARC的话,dealloc函数是不需要实现的,写了反而会出错。
但有些特殊的情况,dealloc函数还是需要的。
比如,在画面关闭的时候,需要把ViewController的某些资源释放,
在viewDidDissppear不一定合适,viewDidUnload一般情况下只在memory warning的时候才被调用。
不用ARC的情况下,我们自然会想到dealloc函数。
其实ARC环境下,也没有把dealloc函数禁掉,还是可以使用的。只不过不需要调用[supper dealloc]了。
举个例子,画面上有UIWebView,它的delegate是该画面的ViewController,在WebView载入完成后,需要做某些事情,比如,把indicator停掉之类的。
如果在WebView载入完成之前关闭画面的话,画面关闭后,ViewController也释放了。但由于WebView正在载入页面,而不会马上被释放,等到页面载入完毕后,回调delegate(ViewController)中的方法,由于此时ViewController已经被释放,所以会出错。(message sent to deallocated instance)
解决办法是在dealloc中把WebView的delegate释放。
iOS平台的内存使用引用计数的机制,并且引入了半自动释放机制;这种使用上的多样性,导致开发者在内存使用上非常容易出现内存泄漏和内存莫名的增长情况; 本文会介绍iOS平台的内存使用原则与使用陷阱; 深度剖析autorelease机制;低内存报警后的处理流程;并结合自身实例介绍内存暴增的问题追查记录以及相关工具的使用情况;
TAG
内存暴增,内存泄漏,autorelease;内存报警;
iOS平台内存常见问题
作为iOS平台的开发者,是否曾经为内存问题而苦恼过?内存莫名的持续增长,程序莫名的crash,难以发现的内存泄漏,这些都是iOS平台内存相关的常见问题;本文将会详细介绍iOS平台的内存管理机制,autorelease机制和内存的使用陷阱,这些将会解决iOS平台内存上的大部分问题,提高了程序的稳定性;
1 iOS平台内存管理介绍
iOS平台的内存管理采用引用计数的机制;当创建一个对象时使用alloc或者allWithZone方法时,引用计数就会+1;当释放对象使用release方法时,引用计数就是-1;这就意味着每一个对象都会跟踪有多少其他对象引用它,一旦引用计数为0,该对象的内存就会被释放掉;另外,iOS也提供了一种延时释放的机制AutoRelease,以这种方式申请的内存,开发者无需手动释放,系统会在某一时机释放该内存; 由于iOS平台的这种内存管理的多样性,导致开发者在内存使用上很容易出现内存泄漏或者程序莫名崩溃的情况,本文会详细介绍iOS平台内存的使用规范与技巧以及如何利用工具避免或者发现问题;
下图是内存从申请到释放的一个完整示例:
2 iOS平台内存使用原则
2.1 对象的所有权与销毁
2.1.1 谁创建,谁释放;
如果是以alloc,new或者copy,mutableCopy创建的对象,则必须调用release或者autorelease方法释放内存;
如果没有释放,则导致内存泄漏!
2.1.2 谁retain,谁释放;
如果对一个对象发送 retain消息,其引用计数会+1,则使用完必须发送release或者autorelease方法释放内存或恢复引用计数;
如果没有释放,则导致内存泄漏!
2.1.3 没创建且没retain,别释放;
不要释放那些不是自己alloc或者retain的对象,否则程序会crash;
不要释放autorelease的对象,否则程序会crash;
2.2 对象的深拷贝与浅拷贝
一般来说,复制一个对象包括创建一个新的实例,并以原始对象中的值初始化这个新的实例。复制非指针型实例变量的值很简单,比如布尔,整数和浮点数。复制指 针型实例变量有两种方法。一种方法称为浅拷贝,即将原始对象的指针值复制到副本中。因此,原始对象和副本共享引用数据。另一种方法称为深拷贝,即复制指针 所引用的数据,并将其赋给副本的实例变量。
2.2.1 深拷贝
深拷贝的流程是 先创建一个新的对象且引用计数为1,并用旧对象的值初始化这个新对象;
ClassA* objA = [[ClassA alloc] init];
ClassA* objB = [objAcopy];
objB是一个新对象,引用计数为1,且objB的数据等同objA的数据;
注意:objB需要释放,否则会引起内存泄漏!
2.2.2 浅拷贝
浅拷贝的流程是,无需引入新的对象,把原有对象的引用计数+1即可
ClassA* objA = [[ClassA alloc] init];
ClassA* objB = [objAretain];
注意:objB需要释放,恢复objA的引用计数,否则会引起内存泄漏!
2.3对象的存取方法
2.3.1 属性声明和实现
变量声明的常用属性类型包括readonly,assign,retain和copy;且系统会自动为声明了属性的变量生成set和get函数;
readonly属性: 只能读,不能写;
assign属性: 是默认属性,直接赋值,没有任何保留与释放问题;
retain属性: 会增加原有对象的引用计数并且在赋值前会释放原有对象,然后在进行赋值;
copy属性: 会复制原有对象,并在赋值前释放原有对象,然后在进行赋值;
2.3.2 使用属性声明可能带来的隐患
当一个非指针变量使用retain(或者copy)这个属性时,尽量不要显性的release这个变量;直接给这个变量置空即可;否则容易产生过度释放,导致程序crash; 例如:
ClassA类的strName是NSString* 类型的变量且声明的属性为retain;
ClassA.strName = nil; /* 释放原有对象且对此对象赋值为空 */
[ClassA.strName release]; /* strName内存可能已经被释放过了,将导致程序crash*/
Assign这个属性一般是非指针变量(布尔类型,整形等)时用这个类型;属于直接赋值型,不需要考虑内存的保留与释放;
如果一个指针类型的变量使用assign类型的属性,有可能引用已经释放的变量;导致程序crash; 例如:
ClassB* obj =[[[ClassB alloc] init]autorelease];
……
ClassA.strName = obj; /* strName 指向obj的内存地址*/
后续在使用ClassA.strName的时候, 因为obj是autorelease的,可能obj的内存已经被回收;导致引用无效内存,程序crash;
3iOS平台AutoRelease机制
3.1 自动释放池的常见问题
大家在开发iOS程序的时候,是否遇到过在列表滑动的情况内存莫名的增长,频繁访问图片的时候内存莫名的增长,频繁的打开和关闭数据库的时候内存莫名的增长…… 这些都是拜iOS的autorelease机制所赐;具体分析如下:
1: 滑动列表的时候,内存出现莫名的增长,原因可能有如下可能:
1:没有使用UITableView的reuse机制; 导致每显示一个cell都用autorelease的方式重新alloc一次;导致cell的内存不断的增加;
2:每个cell会显示一个单独的UIView, 在UIView发生内存泄漏,导致cell的内存不断增长;
2: 频繁访问图片的时候,内存莫名的增长;
频繁的访问网络图片,导致iOS内部API,会不断的分配autorelease方式的buffer来处理图片的解码与显示; 利用图片cache可以缓解一下此问题;
3: 频繁打开和关闭SQLite,导致内存不断的增长;
在进行SQLite频繁打开和关闭操作,而且读写的数据buffer较大,那么SQLite在每次打开与关闭的时候,都会利用autorelease的方式分配51K的内存; 如果访问次数很多,内存马上就会顶到几十兆,甚至上百兆! 所以针对频繁的读写数据库且数据buffer较大的情况,可以设置SQLite的长连接方式;避免频繁的打开和关闭数据库;
3.2 自动释放池的概念
NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声名为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool中
NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数组,release数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。
3.3 自动释放池的作用域与嵌套
AutoreleasePool是可以嵌套使用的!
池是被嵌套的,嵌套的结果是个栈,同一线程只有当前栈顶pool实例是可用的:
当短生命周期内,比如一个循环中,会产生大量的临时内存,可以创建一个临时的autorelease pool,这样可以达到快速回收内存的目的;
3.4 自动施放池的手动创建与自动创建
3.4.1 需要手动创建自动释放池
●如果你正在编写一个不是基于Application Kit的程序,比如命令行工具,则没有对自动释放池的内置支持;你必须自己创建它们。
●如果你生成了一个从属线程,则一旦该线程开始执行,你必须立即创建你自己的自动释放池;否则,你将会泄漏对象。
●如果你编写了一个循环,其中创建了许多临时对象,你可以在循环内部创建一个自动释放池,以便在下次迭代之前销毁这些对象。这可以帮助减少应用程序的最大内存占用量。
3.4.2 系统自动创建自动释放池
Application Kit会在一个事件周期(或事件循环迭代)的开端—比如鼠标按下事件—自动创建一个自动释放池,并且在事件周期的结尾释放它.
4 iOS平台内存使用陷阱
4.1 重复释放
在前文已经提到,不要释放不是自己创建的对象;
释放自己的autorelease对象,app会crash;
释放系统的autorelease对象,app会crash;
4.2 循环引用
循环引用,容易产生野引用,内存无法回收,最终导致内存泄漏!可以通过弱引用的方式来打破循环引用链;所谓的弱引用就是不需要retain,直接赋值的方式,这样的话,可以避免循环引用的问题,但是需要注意的是,避免重复释放的问题;
5 iOS平台内存报警机制
由于iOS平台的内存管理机制,不支持虚拟内存,所以在内存不足的情况,不会去Ram上创建虚拟内存;所以一旦出现内存不足的情况,iOS平台会通知所有已经运行的app,不论是前台app还是后台挂起的app,都会收到 memory warning的notice;一旦app收到memory warning的notice,就应该回收占用内存较大的变量;
5.1 内存报警处理流程
1: app收到系统发过来的memory warning的notice;
2: app释放占用较大的内存;
3: 系统回收此app所创建的autorelease的对象;
4: app返回到已经打开的页面时,系统重新调用viewdidload方法,view重新加载页面数据;重新显示;
5.2 内存报警测试方法
在Simulate上可以模拟低内存报警消息;
iOS模拟器 -> 硬件 -> 模拟内存警告;
开发者可以在模拟器上来模拟手机上的低内存报警情况,可以避免由于低内存报警引出的app的莫名crash问题;
6 iOS平台内存检查工具
6.1 编译和分析工具Analyze
iOS的分析工具可以发现编译中的warning,内存泄漏隐患,甚至还可以检查出logic上的问题;所以在自测阶段一定要解决Analyze发现的问题,可以避免出现严重的bug;
内存泄漏隐患提示:
Potential Leak of an object allocated on line ……
数据赋值隐患提示:
The left operand of …… is a garbage value;
对象引用隐患提示:
Reference-Counted object is used after it is released;
以上提示均比较严重,可能会引起严重问题,需要开发者密切关注!
6.2 内存检测工具
6.2.1 内存泄漏检测工具—Leak
Leak工具可以很容易的统计所有内存泄漏的点,而且还可以显示在那个文件,哪行代码有内存泄漏,这样定位问题比较容易,也比较方面;但是Leak在统计内存泄漏的时候会把autorelease方式的内存也统计进来; 所以我们在查找内存泄漏情况的时候,可以autorelease的情况忽略掉;
Leak工具:
通过Leak工具可以很快发现代码中的内存泄漏,通过工具也可以很快找到发生内存泄漏的代码段:
6.2.2 内存猛增检测工具—Allocations
Allocations工具可以很容易的列出所有分配内存的点,这样我们可以按照分配内存大小来进行排序, 这样可以很容易的发现哪些点分配的内存最多,而且是持续分配,这样我们来针对性的分析这些持续分配较大内存的地方;
此工具会显示出所有申请内存的地方,并统计申请的次数和大小; 从这个列表中可以找出内存申请次数最多且申请内存最大的语句;从而分析出哪些地方使用的内存最多,进而可以优化和改进;
上图是按照申请内存多少来排序的,可以方便的了解哪些代码申请的内存多;
7 参考资料
http://www.cocoachina.com/bbs/read.php?tid=15963
http://developer.apple.com/library/IOs/navigation/