一、XCode4.2以后支持自动释放内存ARC
二、Objcet-C 关于xCode内存管理中内存分配和释放的一些经验
即使是高手也难免会有意无意的造成内存泄漏
三、视图加载
loadView:
(加载视图)
- 建立层次结构
- 在不使用 Interface Builder 的时候发生
viewDidLoad:
(视图已加载)
- 加载附加的资源和数据
viewWillAppear:
(视图快要被显示)
- 准备在屏幕上加载
- 视图不会在每次显示重新加载
viewDidAppear:
(视图已被显示)
- 动画和其他视觉元素被加载
---------------------------------------------------------------------------------------------------------------------
由init、loadView、viewDidLoad、viewDidUnload、dealloc的关系说起
init方法
在init方法中实例化必要的对象(遵从LazyLoad思想)
init方法中初始化ViewController本身
loadView方法
当view需要被展示而它却是nil时,viewController会调用该方法。不要直接调用该方法。
如果手工维护views,必须重载重写该方法
如果使用IB维护views,必须不能重载重写该方法
loadView和IB构建view
你在控制器中实现了loadView方法,那么你可能会在应用运行的某个时候被内存管理控制调用。 如果设备内存不足的时候, view 控制器会收到didReceiveMemoryWarning的消息。 默认的实现是检查当前控制器的view是否在使用。 如果它的view不在当前正在使用的view hierarchy里面,且你的控制器实现了loadView方法,那么这个view将被release, loadView方法将被再次调用来创建一个新的view。
viewDidLoad方法
viewDidLoad 此方法只有当view从nib文件初始化的时候才被调用。
重载重写该方法以进一步定制view
在iPhone OS 3.0及之后的版本中,还应该重载重写viewDidUnload来释放对view的任何索引
viewDidLoad后调用数据Model
viewDidUnload方法
当系统内存吃紧的时候会调用该方法(注:viewController没有被dealloc)
内存吃紧时,在iPhone OS 3.0之前didReceiveMemoryWarning是释放无用内存的唯一方式,但是OS 3.0及以后viewDidUnload方法是更好的方式
在该方法中将所有IBOutlet(无论是property还是实例变量)置为nil(系统release view时已经将其release掉了)
在该方法中释放其他与view有关的对象、其他在运行时创建(但非系统必须)的对象、在viewDidLoad中被创建的对象、缓存数据等 release对象后,将对象置为nil(IBOutlet只需要将其置为nil,系统release view时已经将其release掉了)
一般认为viewDidUnload是viewDidLoad的镜像,因为当view被重新请求时,viewDidLoad还会重新被执行
viewDidUnload中被release的对象必须是很容易被重新创建的对象(比如在viewDidLoad或其他方法中创建的对象),不要release用户数据或其他很难被重新创建的对象
dealloc方法
viewDidUnload和dealloc方法没有关联,dealloc还是继续做它该做的事情
四、iOS程序内存管理
一、非ARC方式,即手动引用计数方式:
这种方式使用引用计数来控制对象的释放
1.alloc,copy,retain三个函数(包括名字中带有着三个词的函数)每调用一次引用计数加1
alloc:分配一块内存用于存放调用类的对象
retain:保有这个对象,通过使其引用计数加1来实现保有,retain返回的对象的指针如果不调用release,这个对象的引用计数至少还有1,同个这样来控制保有这个对象
copy:新开辟一块内存,把调用对象拷贝到里面,copy返回的对象跟被copy的对象完全独立,改变其中一个不会影响另一个
2.release,autorelease每调用一次,引用计数减1:
autorelease:
ClassA *Func1()
{
ClassA *obj = [[[ClassA alloc] init] autorelease];
return obj;
}
每一个runloop,系统会隐式创建一个autorelease pool,每个runloop结束时,对应autorelease pool会销毁,pool中每个object都会release一次
一次事件就产生一个runloop,如一次鼠标点击,键盘点击,一次触摸,一次异步http连接并接收完数据等
也可以自己创建autorelease pool,比如在一个循环中用到了太多的临时变量,在整个循环结束(或每一次循环结束)后想让其自动release:
void main()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *args = [[NSProcessInfo processInfo] arguments];
unsigned count, limit = [args count];
for (count = 0; count < limit; count++)
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
NSString *fileContents;
NSString *fileName;
fileName = [args objectAtIndex:count];
fileContents = [[[NSString alloc] initWithContentsOfFile:fileName] autorelease];//调用了autorelease,才会进入autorelease pool
// this is equivalent to using stringWithContentsOfFile:
/* Process the file, creating and autoreleasing more objects. */
[loopPool release];
}
/* Do whatever cleanup is needed. */
[pool drain];
exit (EXIT_SUCCESS);
}
drain和release在引用计数环境中(ios)是一样的,release自身的同时遍历pool中所有对象进行release,在垃圾回收环境中(mac,ios不支持垃圾回收),release不做任何事情,drain则能触发垃圾回收,所以,一般用drain,以后的兼容性更好
autorelease的执行效率并不高,不能马上释放需要释放的内存,可能导致积压很多需要释放的内存,所以尽量不用用autorelease
3.引用计数等于0时,会调用对象的dealloc用于析构对象,dealloc并不影响引用计数,永远不要显式调用它,要完全依赖引用计数机制
4.NSString/set等容器在对象加入后,对象的引用计数会加1以得到其控制权,移除对象时,对应引用计数减1,UIView对subView的控制,UINavigationController对其内的controller等其他类似容器的对象对其子对象也是一样的机制
5.循环引用:
a对象中一个方法alloc了一个b对象,并调用b对象的一个方法funb,b对象的funb中,alloc了一个c对象,调用了c对象的func,在c对象的func中,又alloc了一个b对象
这样下来,b对象的引用计数为2,c对象的引用计数为1
a对象处理完后调用b对象的release,本来希望释放掉b对象(a对b alloc一次,按理只要release一次就可以释放b),但是由于循环引用(b和c互相引用),导致b对象release一次后,引用计数还有1,没有释放,b和c都是在dealloc中释放对方的,b没有调用dealloc,c也就不会调用dealloc,这样就形成了b和c的内存泄漏
如何避免循环引用造成的内存泄漏呢:
以delegate模式为例(viewcontroller和view之间就是代理模式,viewcontroller有view的使用权,viewcontroller同时也是view的代理(处理view中的事件)):
UserWebService.h
#import<UIKit/UIKit.h>
//定义一个ws完成的delegate
@protocol WsCompleteDelegate
@required
-(void) finished;//需要实现的方法
@interface UserWebService:NSObject
{
id <WsCompleteDelegate> delegate;//一个id类型的dategate对象
}
@property (assign) id <WsCompleteDelegate> delegate;
-(id)initWithUserData:(User *)user;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
#import<UIKit/UIKit.h>
//定义一个ws完成的delegate
@protocol WsCompleteDelegate
@required
-(void) finished;//需要实现的方法
@end
@interface UserWebService:NSObject
{
id <WsCompleteDelegate> delegate;//一个id类型的dategate对象
}
@property (assign) id <WsCompleteDelegate> delegate;
-(id)initWithUserData:(User *)user;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
@end
UserWebService.m:
#import <Foundation/Foundation.h>
@systhesize delegate;//同步这个delegate对象
@implementation UserWebService
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[delegate finished]
}
@end
LoginViewController.h:
#import "UserWebService.h" //包含含有委托的头文件
@interface LoginViewController:UIViewController <WsCompleteDelegate>
-(void)submit;
@end
LoginViewController.m:
@implementation LoginViewController
-(void) submit
{
User *user = [[User alloc]init];
[user setUserId:@"username"];
[user setPassword:@"password"];
ws = [[UserWebService alloc] initWithUserData:user];
ws.delegate = self;//设置委托的收听对象
[user release];
[ws send];
}
//实现委托中的方法,
-(void) finished
{
NSAttry *users = [ws users];
}
@end
可以看到,delegate声明为assign:
@property (assign) id <WsCompleteDelegate> delegate;
如果声明为retain会如何?
LoginViewController alloc了一个UserWebService,UserWebService的代理又是LoginViewController,这样就循环引用了:
ws = [[UserWebService alloc] initWithUserData:user]; //UserWebService对象引用计数加1
ws.delegate = self;//LoginViewController对象引用计数加1
外部框架allocLoginViewController对象后,LoginViewController对象的引用计数为2 ,release后还是无法销毁,产生内存泄漏
所以用assign而不是retain声明属性可以避免循环引用,ARC下用弱引用也可以解决
6.内存检测可以用xcode继承的instrument工具,不过循环引用引起的内存泄露是检测不出来的
7.对象release到引用计数为0后,如果对应指针没有赋值为nil,怎出现野指针
8.ClassA *a = [[ClassA alloc] init];
a = nil;//alloc的内存没有任何对象可以控制它了,引用计数永远为1,这就造成了内存泄露
9.简单的赋值操作并不会改变对象的引用计数:
ClassA *a = [[ClassA alloc] init];
ClassA *b = a;//a和b指向的对象的引用计数还是1
10.@property:
默认为@property为@property(atomic,assign)
nonatomic: 没有对应的atomic关键字,即使上面是这么写,但atomic叧是在你没有声明这个特性的时候系统默认,你无法主动去声明这一特性。nonatomic不支持多线程访问,atomic有同步机制,支持多线程访问,如果需要多线程访问,声明为atomic(维持默认),否则声明为nonatomic,因为nonatomic效率比atomic高得多
关于assign、retain和copy: assign是系统默认的属性特性,它几乎适用亍OC的所有变量类型。对于非对象类型的变量,assign是唯一可选的特性。但是如果你在引用计数下给一个对象类型的变量声明为assign,那么你会在编译的时候收到一条来自编译器的警告。因为assign对于在引用计数下的对象特性,叧创建了一个弱引用(也就是平时说的浅复制)。返样使用变量会很危险。当你release了前一个对象的时候,被赋值的对象指针就成了无头指针了。因此在为对象类型的变量声明属性的时候,尽量少(或者不要)使用assign。
关于assign合成的setter,看起来是这样的:
-(void)setObjA:(ClassA *)a {
objA = a;
}
在深入retain之前,先把声明为retain特性的setter写出来:
-(void)setObjA:(ClassA *)a
{
If(objA != a)
{
[objA release];
objA = a;
[objA retain]; //对象的retain count 加1
}
}
明显的,在retain的setter中,变量retain了一次,那么,即使你在程序中 self.objA = a; 只写了这么一句,objA仍然需要release,才能保证对象的retain count 是正确的。但是如果你的代码 objA = a; 叧写了这么一句,那么这里只是进行了一次浅复制,对象的retain count 并没有增加,因此这样写的话,你不需要在后面release objA。 这2句话的区别是,第一句使用了编译器生成的setter来设置objA的值,而第二句叧是一个简单的指针赋值
11.NSString *str = [[NSString alloc] initwithstring @“abc”];
str = @“abcd”;‘
[str release];
NSLog("%@",str);//打印出abcd
str为什么没有变成野指针呢?因为字符串常量(包括NSString和@“......”)的引用计数很大(100K+),基本上不会释放掉(由OC自己管理),所以字符串常量可以不用release
二、ARC(auto release count):自动引用计数方式:
这种方式使用强引用和弱引用来控制对象的释放,只要对象的强引用计数为0,对象就立即释放,变量默认为强引用
强/弱引用的数量是指向这个对象的强/弱指针的数量
弱引用:
在ARC方式下:
NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; |
NSLog(@"string: %@", string); |
打印出来的是null,因为这个NSString对象创建完成后,没有任何强引用指向它,所以立即释放了,并赋值为nil
__unsafe_unretained:弱引用变量声明,等同于assign
__weak:声明了一个可以自动nil化的弱引用,释放后,变量自动置为nil
__strong:强引用,等同于retain
ARC在IOS4.0及以后的系统都可以用,但是weak声明必须在ios5及以后才可以用,如果启用了RAC,选择目标运行平台为ios4,xcode会自动插入一个兼容库到工程中,以实现自动兼容
ARC引入的新规则:
ARC能够起作用引入了一些新的规则。这些规则定义了所有的内存管理方方面面,某些规则是为了更好的体验,还有一些规则是为了减少编程人员在内存管理方面的工作。如果你违反这些规则,就会得到编译期的错误,而不是运行期的错误。
-
你不能显示调用dealloc,不能实现和显示调用retain,release,retainCount和autorelease。
当然也不能使用@selector(retain),@selector(release)等相关的功能。
-
你不能在C结构中使用实例指针。
与其使用结构,建议你使用Objective-C类来管理数据。
-
id和void *不能进行隐式转换。
你必须显式的告诉编译器这个转换的类型。
-
不能使用NSAutoreleasePool的实例.
作为替代,ARC提供@autoreleasepool块作为替代。后者提供了更灵活的方式。
为了和手动管理内存相兼容,ARC定义了函数和变量命名一条规则:
属性变量的命名不能使用new开始。
五、在高版本xcode中编译非ARC代码
在创建Xcode项目的时候选择了支持ARC以后,开发的时候代码里就不能有手动处理内存释放的代码。
但是有时候需要导入一些手动处理内存释放的库或者以前的源代码。
这时候可以将这些源代码在编译的时候不进行ARC编译。
设定方法如下:
1. 在Xcode的项目里点击项目
2. 在TARGETS里选择你的项目目标文件,然后选择Build Phases
3. 在Compile Sources里面“双击”不需要ARC的代码文件
4. 在弹出的对话框里输入-fno-objc-arc,按done按钮。
六、iOS从手动管理内存过渡到使用ARC
参考Apple Developer原文:Transitioning to ARC Release Notes
1. ARC简介:
使用ARC之后就永远也不要自己手动去写retain, release和autorelease了,编译器会自动帮你完成这些事情。既然这些手动操作都不需要我们来写了,dealloc方法也是会自动完成的,如果没有特殊资源要手动销毁,dealloc方法实际上我们也不需要写了。自动完成?那会不会因为某些问题导致性能低下?事情证明完全不需要为这个担心,ARC已经是比较成熟的技术了,而且是在编译期间就在合适的位置自动加上内存管理代码了。iOS4以上的系统都是支持ARC的,不过要额外一提的是iOS4无法支持弱引用(weak reference)。
拿官方的举例来说一下,实现一个记录Person数据的类,现在只要这么些代码:
1
2
3
4
5
6
7
8
9
|
@interfacePerson:NSObject
@propertyNSString *firstName;
@propertyNSString *lastName;
@propertyNSNumber *yearOfBirth;
@propertyPerson *spouse;
@end
@implementation Person
@end
|
(注意,这里的property都没有标注属性,默认都会是strong类型的,strong类型后面会介绍)
然后,你可以像下面这样实现一个方法而完全不需要管内存的问题:
1
2
3
4
5
6
7
|
-(void)contrived{
Person *aPerson=[[Person alloc]init];
[aPerson setFirstName:@"William"];
[aPerson setLastName:@"Dudney"];
[aPerson setYearOfBirth:[[NSNumber alloc]initWithInteger:2011]];
NSLog(@"aPerson: %@",aPerson);
}
|
你也可以像下面这样实现一个方法而不用担心变量被过早的释放:
1
2
3
4
5
|
-(void)takeLastNameFrom:(Person *)person{
NSString *oldLastname=[selflastName];
[selfsetLastName:[person lastName]];
NSLog(@"Lastname changed from %@ to %@",oldLastname,[selflastName]);
}
|
总体来说真的是让代码变得无比的简洁,以后应该说大部分应用使用ARC是一个必然的趋势。
2. 使用ARC的强制规则
- 不可以显式的调用dealloc([super dealloc]已经不需要了),不可以调用或者实现release, retain和autorelease。当然使用@selector去调用这些方法也是不允许的。因为前面的规定,实际上不需要在dealloc里面去释放一些ARC无法管理的资源时,完全不需要写dealloc了。针对一些Core Foundation型的对象,CFRetain和CFRelease还是可以用的。
- NSAllocateObject和NSDeallocateObject不再可以被使用,用alloc去创建对象就好。
- 在C结构的代码里不能再使用对象指针了,需要用ObjC类来管理你的data了。
- id类型和void *类型不会有隐式转换了,详细的一些转换操作要参考别的章节,这里就先不赘述了。“Managing Toll-Free Bridging”
- NSAutoreleasePool无法再用了,可以使用新的更灵活的@autoreleasepool block来替代。
- memory zones也无法用了,因为现在已经不需要NSZone了。
- 访问器不可以以new开头,除非你手动指定一个不是new开头的getter。如下:
1
2
3
4
5
|
// Won't work:
@propertyNSString *newTitle;
// Works:
@property(getter=theNewTitle)NSString *newTitle;
|
3. ARC里新增的生命期定义
属性的特性(Property Attributes):
1
2
3
4
5
6
|
// 下面的定义相当于以前: @property(retain) MyClass *myObject;
@property(strong)MyClass *myObject;
// 下面的定义类似于以前的: @property(assign) MyClass *myObject;"
// 唯一不同的是,一般myObject被释放了,属性将被置nil,而不再会成为野指针
@property(weak)MyClass *myObject;
|
变量的修饰符(Variable Qualifiers):
__strong 是默认值,对象将被维持在内存。一个强引用阻止指向的对象被释放,相当于给对象retain了一次。
__weak 指定了一个弱引用,并不会阻止对象被销毁。当这个对象没有被其它地方强引用的话,它就会被销毁,并且弱引用对应的变量会被置nil。
__unsafe_unretained 这是针对iOS4使用的“弱引用”版本。为什么加上了引号,因为和真正的弱引用区别在于,一旦指向的对象被销毁了,变量也会变成野指针。所以说也看到加了unsafe字样。
__autoreleasing 用来指示通过引用传递的参数(就是非值传递),将会在return的时候被自动释放一个计数,这个一般我们不需要去用。
使用的方法举个例子如下:
1
2
|
MyClass *__weak myWeakReference;
MyClass *__unsafe_unretained myUnsafeReference;
|
需要注意,使用弱引用的时候必须要十分的小心,看下面的例子:
1
2
|
NSString *__weak string=[[NSString alloc]initWithFormat:@"First Name: %@",[selffirstName]];
NSLog(@"string: %@",string);
|
没有被强引用的变量是会被立即释放的,所以这里的string在紧接的下面一句,就已经变成nil了。
这里顺带介绍一下之前就有的一些属性特性(Property Attributes):
retain:相当于目前的strong。
assign:相当于目前的weak,主要还是用在一些元数据类型int、BOOL之类的。
copy:比较特殊,是首先为对象创建了一个副本,然后对副本拥有strong的特性。
4. 用编译标志来开启和禁用ARC
当然如果是创建新的工程只要在创建的时候勾上使用ARC的勾就好。如果是在没有开启ARC的旧工程里,可以用新的编译标志 -fobjc-arc 来为单独文件开启ARC。而如果是在已经开了ARC的工程里,可以对不需要使用ARC的指定单独文件用 -fno-objc-arc 编译标志来为其禁用ARC。编译标志的添加方法是在工程属性界面TARGETS->Build Phases->Compile Sources里选中指定的文件后双击,就会看到编译标志的编辑框了,编辑完之后回车就好。
七、IOS开发之内存管理--dealloc该写些什么
在非ARC开发环境中,dealloc是类释放前,清理内存的最后机会。到底那些变量和属性该释放呢,一些特殊的类(nstimer,observer)该怎么释放。需要注意的是不释放会引起内存泄露,过度释放也会引起内存泄露,接下来会慢慢展开:
1 变量的释放
变量声明
@interface EnterHondaViewController : UIViewController{
UIImageView * imageLogo;
UIButton * btn_Corporate;
UIButton * btn_Brand;
CorporateView * corporateview;
BrandView * brandview;
}
变量初始化
@implementation EnterHondaViewController
-(id)initWithFrame:(CGRect)frame
{
self = [super init];
if (self) {
self.view.frame = frame;
[self.view setBackgroundColor:[UIColor whiteColor]];
UIImageView * background = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0,1024, 768)];
[self.view addSubview:background];
[background release];
[background setImage:[UIImage imageNamed:@"AllAuto_Image_BG"]];
UIButton * backBtn = [[UIButton alloc]initWithFrame:CGRectMake(50, 18, 55,40)];
[backBtn setImage:[UIImage imageNamed:@"home_button"] forState:UIControlStateNormal];
[backBtn addTarget:self action:@selector(onBack:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:backBtn];
[backBtn release];
UILabel * titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(160, 25, 400, 35)];
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.textAlignment = NSTextAlignmentLeft;
titleLabel.font = [UIFont fontWithName:@"Arial" size:30];
titleLabel.textColor = [UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0];
[self.view addSubview:titleLabel];
[titleLabel release];
[titleLabel setText:@"走进广本"];
UIImageView * lineView = [[UIImageView alloc] initWithFrame:CGRectMake((frame.size.width-658)/2,70, 658, 8)];
lineView.image = [UIImage imageNamed:@"AllAuto_Config_TimeLine_BG"];
[self.view addSubview:lineView];
[lineView release];
UIView * logoview = [[UIView alloc] initWithFrame:CGRectMake(780, 80, 240, 25)];
[self.view addSubview:logoview];
[logoview release];
imageLogo = [[UIImageView alloc] initWithFrame:CGRectMake(0, 6, 12, 13)];
[logoview addSubview:imageLogo];
[imageLogo release];
imageLogo.image = [UIImage imageNamed:@"AllAuto_Corporation_Button_Select"];
btn_Corporate = [[UIButton alloc] initWithFrame:CGRectMake(13, 0, 100, 25)];
[logoview addSubview:btn_Corporate];
btn_Corporate.tag = 1;
[btn_Corporate release];
[btn_Corporate addTarget:self action:@selector(onOptionClick:) forControlEvents:UIControlEventTouchUpInside];
[btn_Corporate setTitle:@"企业文化" forState:UIControlStateNormal];
btn_Brand = [[UIButton alloc] initWithFrame:CGRectMake(133, 0, 100, 25)];
[logoview addSubview:btn_Brand];
btn_Brand.tag = 3;
[btn_Brand release];
[btn_Brand addTarget:self action:@selector(onOptionClick:) forControlEvents:UIControlEventTouchUpInside];
[btn_Brand setTitle:@"品牌故事" forState:UIControlStateNormal];
corporateview = [[CorporateView alloc] initWithFrame:CGRectMake(0, 110, frame.size.width, frame.size.height-120)];
brandview = [[BrandView alloc] initWithFrame:CGRectMake(0, 110, frame.size.width, frame.size.height-110)];
[btn_Corporate sendActionsForControlEvents:UIControlEventTouchUpInside];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onGoBrandPage:) name:Notification_Navigate_To_Brand object:nil];
}
return self;
}
变量释放
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self];
[brandview release];
[corporateview release];
[super dealloc];
}
2 属性的释放
属性声明
@interface GGDetailCell : UIGridViewCell {
}
@property (nonatomic, retain) UILabel *labelTitle;
@end
属性初始化
- (id)init {
if (self = [super init]) {
self.frame = CGRectMake(0, 0, 66, 30.5);
{ // labelTitle
self.labelTitle = [[[UILabel alloc] initWithFrame:CGRectMake(0, 7, 66, 16)] autorelease];
[self.labelTitle setBackgroundColor:[UIColor clearColor]];
[self.labelTitle setTextColor:TEXT_COLOR_PART_NORMAL];
[self.labelTitle setFont:[UIFont systemFontOfSize:12]];
[self.labelTitle setNumberOfLines:0];
[self.labelTitle setTextAlignment:UITextAlignmentCenter];
[self addSubview:self.labelTitle];
}
}
return self;
}
属性释放
- (void)dealloc {
self.labelTitle = nil;
[super dealloc];
}
3 定时器的释放
定时器声明:
@interface BRLandscapeView
NSTimer* timer;
}
@end
定期初始化:
-(void) myInit{
{//set timer
timer = [NSTimer scheduledTimerWithTimeInterval: 1
target: self
selector: @selector(handleTimer:)
userInfo: nil
repeats: YES];
}
定时器释放:如果实在view中声明初始化的,要在 controller中view释放前先释放定时器,否则由于循环引用,而释放不掉
@implementation BookReadController
-(void)dealloc
{
if (landscape) {
[landscape->timer invalidate];
}
SafeRelease(landscape);
[super dealloc];
}
@end
4 通知的释放
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self];
[super dealloc];
}
5 delegate的释放
delegate属性的赋值一般为self,虽然声明时assign,但在相关的view释放时,在之前先释放掉delegate
情况一
if (_loadingContentView) {
_loadingContentView.delegate = nil;
[_loadingContentView removeFromSuperview];
}
情况二
self.partGridView.uiGridViewDelegate = nil;
self.partGridView = nil;
6 有返回值的函数的内存管理
如果一个函数需要一个返回值,此返回值在函数内声明和初始化,但缺不能立即释放,最好的处理方式如下:
-(NSArray*)getTemplatesByPath:(NSString *)path
{
NSError * error = nil;
NSArray* files = [fileManager contentsOfDirectoryAtPath:path error:&error];
if(error != nil)
return nil;
NSMutableArray* resultArray = [NSMutableArray new];
for (NSInteger index=0; index < [files count]; index++)
{
NSString* fileName = [files objectAtIndex:index];
NSString* extType = [fileName pathExtension];
if(NO == [extType isEqualToString:@"tpl"])
continue;
[resultArray addObject:fileName];
}
return [resultArray autorelease];
}
扩展
1 变量初始化完,用完可立即释放的情况
UIImageView * lineView = [[UIImageView alloc] initWithFrame:CGRectMake((frame.size.width-658)/2,70, 658, 8)];
lineView.image = [UIImage imageNamed:@"AllAuto_Config_TimeLine_BG"];
[self.view addSubview:lineView];
[lineView release];
2 声明时,是声明为变量还是属性的考量。
在ios第一版中,我们为输出口同时声明了属性和底层实例变量,那时,属性是oc语言的一个新的机制,并且要求你必须声明与之对应的实例变量,例如:
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
最近,苹果将默认编译器从GCC转换为LLVM(low level virtual machine),从此不再需要为属性声明实例变量了。
如果LLVM发现一个没有匹配实例变量的属性,它将自动创建一个以下划线开头的实例变量。因此,在这个版本中,我们不再为输出口声明实例变量。
例如:
MyViewController.h文件
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
在MyViewController.m文件中
编译器也会自动的生成一个实例变量_myButton
那么在.m文件中可以直接的使用_myButton实例变量,也可以通过属性self.myButton.都是一样的。
注意这里的self.myButton其实是调用的myButton属性的getter/setter方法
这与c++中点的使用是有区别的,c++中的点可以直接访问成员变量(也就是实例变量)
例如在oc中有如下代码
.h文件
@interface MyViewController :UIViewController
{
NSString *name;
}
@end
.m文件中
self.name 这样的表达式是错误的。xcode会提示你使用->,改成self->name就可以了。
因为oc中点表达式是表示调用方法,而上面的代码中没有name这个方法。
oc语法关于点表达式的说明:
"点表达式(.)看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,其实这是oc的设计人员有意为之。
如果点表达式出现在等号 = 左边,该属性名称的setter方法将被调用。如果点表达式出现在右边,该属性名称的getter方法将被调用。"
所以在oc中点表达式其实就是调用对象的setter和getter方法的一种快捷方式, 例如:
dealie.blah = greeble 完全等价于 [dealie.blah setBlah:greeble];
以前的用法,声明属性跟与之对应的实例变量:
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
这种方法基本上使用最多,现在大部分也是在使用,因为很多开源的代码都是这种方式。
但是ios5更新之后,苹果是建议以以下的方式来使用
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
因为编译器会自动为你生成以下划线开头的实例变量_myButton。不需要自己手动再去写实例变量。
而且也不需要在.m文件中写@synthesize myButton;也会自动为你生成setter,getter方法。
@synthesize的作用就是让编译器为你自动生成setter与getter方法。
它还有一个作用,可以指定与属性对应的实例变量,
例如@synthesize myButton = xxx;
那么self.myButton其实是操作的实例变量xxx,而不是_myButton了。
在实际的项目中,我们一般这么写.m文件
@synthesize myButton;
这样写了之后,那么编译器会自动生成myButton的实例变量,以及相应的getter和setter方法。
注意:_myButton这个实例变量是不存在的,因为自动生成的实例变量为myButton而不是_myButton。
所以现在@synthesize的作用就相当于指定实例变量,
如果.m文件中写了@synthesize myButton;那么生成的实例变量就是myButton。
如果没写@synthesize myButton;那么生成的实例变量就是_myButton。
所以跟以前的用法还是有点细微的区别。
注意:这里与类别中添加的属性要区分开来,因为类别中只能添加方法,不能添加实例变量。
经常会在ios的代码中看到在类别中添加属性,这种情况下,是不会自动生成实例变量的。
比如在
UINavigationController.h文件中会对UIViewController类进行扩展
@interface UIViewController (UINavigationControllerItem)
@property(nonatomic,readonly,retain) UINavigationItem *navigationItem;
@property(nonatomic) BOOL hidesBottomBarWhenPushed;
@property(nonatomic,readonly,retain) UINavigationController *navigationController;
@end
这里添加的属性,不会自动生成实例变量,这里添加的属性其实是添加的getter与setter方法。
注意一点,匿名类别(匿名扩展)是可以添加实例变量的,非匿名类别是不能添加实例变量的,只能添加方法,或者属性(其实也是方法)。
ARC
关于强引用,弱引用,arc的使用可以查看文件ios5arc完全指南。
ios5引进的arc确实方便了很多,ARC 的规则非常简单:
只要还有一个变量指向对象,对象就会保持在内存中。当指针指向新值,或者指针不再存在时,相关联的对象就会自动释放。
这条规则对于实例变量、synthesize 属性、本地变量都是适用的。
以前没有arc的时候,必须调用
dealloc方法来释放内存,比如:
- (void)dealloc {
[_myTableView release];
[superdealloc];
}
如果使用了ARC,那么将不再需要dealloc方法了,也不需要再担心release问题了。系统将自动的管理内存。
八、iOS中assign、copy 、retain等关键字的含义
assign: 简单赋值,不更改索引计数
copy: 建立一个索引计数为1的对象,然后释放旧对象
retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1
Copy其实是建立了一个相同的对象,而retain不是:
比如一个NSString对象,地址为0×1111,内容为@”STR”
Copy到另外一个NSString之 后,地址为0×2222,内容相同,新的对象retain为1, 旧有对象没有变化
retain到另外一个NSString之 后,地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1
也就是说,retain是指针拷贝,copy是内容拷贝。在拷贝之前,都会释放旧的对象。
* 使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等)
* 使用copy: 对NSString
* 使用retain: 对其他NSObject和其子类
1.readonly表示这个属性是只读的,就是只生成getter方法,不会生成setter方法.
2.readwrite,设置可供访问级别
3.retain,是说明该属性在赋值的时候,先release之前的值,然后再赋新值给属性,引用再加1。
4.nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。
retain和copy还有assign的区别
1. 假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时a和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块内存的时候会引起程序crash掉。
2. 了解到1中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候,代表该内存不再被任何指针所引用,系统可以把它直接释放掉。
3. 上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。
4. copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。
5. atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:
if (property != newValue) {
[property release];
property = [newValue retain];
}
关于retain,copy,assign的区别问题其实困扰我很久了,因为在程序中不太常用到copy,assign,所以三者的具体差别一直不太明白。
按照我的理解,assign和retain的区别,就是引入了一个计数器retaincount,就可以对一个内存的释放方便很多。copy,就是把原来的内存复制一遍,使各自都拥有一个内存,这样释放的时候也不会出错。
assign: 简单赋值,不更改索引计数(Reference Counting)。
copy: 建立一个索引计数为1的对象,然后释放旧对象
retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1
使用assign: 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)
使用copy: 对NSString
使用retain: 对其他NSObject和其子类
nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问
@property(nonatomic, retain) UITextField *userName编译时自动生成的代码
- (UITextField *) userName {
return userName;
}
- (void) setUserName:(UITextField *)userName_ {
[userName release];
userName = [userName_ retain];
}
@property(retain) UITextField *userName自动生成的代码
- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
retval = [[userName retain] autorelease];
}
return retval;
}
- (void) setUserName:(UITextField *)userName_ {
@synchronized(self) {
[userName release];
userName = [userName_ retain];
}
}
九、xCode开发调试技巧总结
一、概述1.掌握调试技巧,调试技术
最基本,最重要的调试手段包括:单步跟踪,断点,变量观察等。
单步跟踪(Step)所谓单步跟踪是指一行一行地执行程序,每执行一行语句后就停下来等待指示,这样你就能够仔细了解程序的执行顺序,以及当时的各种状况。
断点(Breakpoint)断点是调试中非常重要的一个手段。由于在执行到某些代码前需要执行许多其它代码,不可能用单步跟踪一条一条执行过来,这时只要在需要暂停的地方设置一个断点,然后让程序运行,当执行到这个断点位置时不需要用户干预就会暂停并返回集成调试程序.断点必须位于可执行代码行上,凡设置在注释,空白行,变量说明上的都是无效的。另外,断点既可以在设计状态下设置也可以在运行调试状态下设置。根据断点调试找到错误处。在程序开发中,为了找到程序的bug,通常采用的一种调试手段,一步一步跟踪程序执行的流程,根据变量的值,找到错误的原因。 在需要调试的代码断设置断点,然后按预设的快捷键步进。调试状态运行程序,程序执行到有断点的地方会停下来。
2.内存泄漏解释
简单的说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。 一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
使用leaks工具帮助查看内存泄漏问题.在的XCode工具列,Run=>“Run with Perfromance Tool=>Leak 在iPhone程式开发中,使用NSLog直接在控制台印出retainCount也是一个检视內存泄漏的方法,但是的XCode提供了更方便的泄漏工具供开发者使用。 http://blog.csdn.net/cloudhsu/archive/2010/07/22/5754818.aspx (重要)
二、常见错误1.Objective-C EXC_BAD_ACCESS
程序遇到 Bug 并不可怕,大部分的问题,通过简单的 Log 或者 代码分析并不难找到原因所在。但是在 Objective-C 编程中遇到 EXC_BAD_ACCESS 问题的时候,通过简单常规的手段很难发现问题。这篇文章,给大家介绍一个常用的查找 EXC_BAD_ACCESS 问题根源的方法。首先说一下 EXC_BAD_ACCESS 这个错误,可以这么说,90%的错误来源在于对一个已经释放的对象进行release操作。 举一个简单的例子来说明吧,首先看一段Java代码:
- public class Test{
- public static void main(String[] args){
String s = “This is a test string”; - s = s.substring(s.indexOf(“a”),(s.length()));
System.out.println(s); - }
- }
这种写法在Java中很常见也很普遍,这不会产生任何问题。但是到了 Objective-C 中,就会出事,考虑这个程序:
- #import <Foundation/Foundation.h>
- int main (int argc, c*****t char * argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; - NSString* s = [[NSString alloc]initWithString”This is a test string”];
s = [s substringFromIndex:[s rangeOfString"a"].location];//内存泄露 - [s release];//错误释放 [pool drain];//EXC_BAD_ACCESS return 0;
- }
这个例子当然狠容易的看出问题所在,如果这段代码包含在一个很大的逻辑中,确实容易被忽略。Objective-C 这段代码有三个致命问题:1,内存泄露。2,错误释放。3,造成 EXC_BAD_ACCESS 错误。
1,内存泄露。 NSString* s = [[NSString alloc]initWithString”This is a test string”]; 创建了一个 NSString Object, 随后的 s = [s substringFromIndex:[s rangeOfString"a"].location]; 执行后,导致创建的对象引用消失,直接造成内存泄露。
2,错误释放。[s release]; 这个问题,原因之一是一个逻辑错误,以为 s 还是我们最初创建的那个 NSString 对象。 第二是因为从 substringFromIndexNSUInteger i) 这个方法返回的 NSString 对象,并不需要我们来释放, 它其实是一个被 substringFromIndex 方法标记为 autorelease 的对象。如果我们强行的释放了它,那么会造成 EXC_BAD_ACCESS 问题。
3,造成 EXC_BAD_ACCESS 错误。EXC_BAD_ACCESS。由于 s 指向的 NSString 对象被标记为 autorelease, 则在 NSAutoreleasePool 中已有记录。但是由于我们在前面错误的释放了该对象,则当 [pool drain] 的时候,NSAutoreleasePool 又一次的对它记录的 s 对象调用了 release 方法,但这个时候 s 已经被释放不复存在,则 直接导致了 EXC_BAD_ACCESS问题。
那么,知道了 EXC_BAD_ACCESS 的诱因之一后,如何快速高效的定位问题? 1: 为工程运行时加入 NSZombieEnabled 环境变量,并设为启用,则在 EXC_BAD_ACCESS 发生时,XCode 的 C*****ole 会打印出问题描述。 首先双击 XCode 工程中,Executables 下的 可执行模组, 在弹出窗口中,Variables to be set in the environment,添加 NSZombieEnabled,并设定为 YES,点击选中复选框启用此变量。 这样,运行上述 Objective-C 时会看到控制台输出: Untitled[3646:a0f] *** -[CFString release]: message sent to deallocated instance 0x10010d340 这条消息对于定位问题有很好的提示作用。但是很多时候,只有这条提示是不够的,我们需要更多的提示来帮助定位问题,这时候再加入 MallocStackLogging 来启用malloc记录。 当错误发生后,在终端执行: malloc_history ${App_PID} ${Object_instance_addr} 则会获得相应的 malloc 历史记录,比如对于上一个控制台输出 Untitled[3646:a0f] *** -[CFString release]: message sent to deallocated instance 0x10010d340 则我们可以在终端执行 结果如下:
Buick-Wongs-MacBook-Proownloads buick$ malloc_history 3646 0x10010d340 malloc_history Report Version: 2.0 Process: Untitled [3646] Path: /Users/buick/Desktop/Untitled/build/Debug/Untitled Load Address: 0×100000000 Identifier: Untitled Version: ??? (???) Code Type: X86-64 (Native) Parent Process: gdb-i386-apple-darwin [3638] Date/Time: 2011-02-01 15:07:04.181 +0800 OS Version: Mac OS X 10.6.6 (10J567) Report Version: 6 ALLOC 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | +[NSString initialize] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | NXCreateMapTableFromZone | malloc_zone_malloc —- FREE 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | _finishInitializing | free ALLOC 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | -[NSPlaceholderString initWithString:] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | _class_initialize | +[NSMutableString initialize] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | NXCreateMapTableFromZone | malloc_zone_malloc —- FREE 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | -[NSPlaceholderString initWithString:] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | _class_initialize | _finishInitializing | free ALLOC 0x10010d340-0x10010d35f : thread_7fff70118ca0 |start | main | -[NSCFString substringWithRange:] | CFStringCreateWithSubstring | __CFStringCreateImmutableFunnel3 | _CFRuntimeCreateInstance | malloc_zone_malloc 这样就可以很快的定位出问题的代码片段了,注意输出的最后一行,这行虽然不是问题的最终原因,但是离问题点已经很近了,随着它找下去,八成就会找到问题。 当然,EXC_BAD_ACCESS 的定位方法还有很多,随着具体问题的不同而不同。
2. _OBJC_CLASS_$_ errors 造成这个错误,存在两种原因: 1.项目未添加一个 CoreData framework; 2.由于某个或某几个.m文件没有被标记(打钩)的原因造成的; 我的错误原因是第二种原因造成的,我的解决方法是将与服务器端冲突的几个先在项目中删除,然后再将删除的文件重新拖拽到xcode中, 注意在添加文件是要记得打钩。 如果再次运行发现Failed to upload *.app问题,首先请先关闭xcode,然后将项目的build目录删除,再次重新打开xcode,运行程序,问题解决。
3.下面的错误:主要是在程序中加了中文符号 ; 而不是在英文下 ; 引起的错误 在编写程序时,需要注意 一定要在 英文下编写。
- link.c:69: error: stray ‘357’ in program
- link.c:69: error: stray ‘274’ in program
- link.c:69: error: stray ‘233’ in program
- link.c:70: error: expected ‘;’ before ‘insert_elem’
- link.c:70: error: stray ‘357’ in program
- link.c:70: error: stray ‘274’ in program
- link.c:70: error: stray ‘233’ in program
三、iPhone 开发经验教训总结参考
- 所有的UI操作,都要切换到主线程中进行.否则,会发生莫名其妙的错误.在主线程中,runloop默认是开启状态的。非主线程中,如果要用到 runloop,必须手动开启runloop。关于runloop知识。对于常见的 EXEC_BAD_ACCESS,EXC_BAD_INSTRUCTION,错误,一般都是因为访问已经被release的对象造成的。尤其是在一个线程 中访问另外一个线程的autorelease库中的对象,尤其要注意此类问题。严格遵守iphone 内存管理手册,对于不是由你创建的对象,不要越权release,否则,可能会导致程序crash.有时,一些看起来非常严重的bug,在经过N过次努 力,多种思路尝试fix之后,再回头分析bug产生的原因,你会发现,造成这个严重bug的原因,很可能是你违反了一个众所周知的规则引起的.这个规则你 非常清楚,熟悉,但就是在coding的时候,稍不留神违反了它.于是就带来了灾难性后果.除了面向对象的cocoa外,iphone编程不要忘记非面向 对象的Core Foundation。 面向对象库里很多没有的功能,可以尝试在Core Foundation里找找。比如:RSA算法,MD5算法,SHA1算法,AES加密算法等,cocoa对象库里并没有相应的实现,但在core foundation里,均有相应的实现。NSString类里没有的字符串编码GBK,GB2312,GB18030等,在 CoreFoundation里,能找到相应的编码。建立socket连接,获得输入流和输出流时,也需要使用Core Foundation里的CFNetwork api。等等。通过设置NSZombieEnabled参数,有非常有效帮助解决内存释放错误。在消除某个对象时,如果为该对象设置了delegate, 则需要先将delegate设成nil,这是一种良好的代码习惯。
- 在3.0 的Simulator上使用Instruments 检测内存泄漏时,无法看到函数名,只能看到一些地址指针.在3.1,3.1.2,3.1.3的simulator都正常,能够正常地看到是在哪个函数中存 在的内存泄漏.通过Nib文件加载viewcontroller的各种UI控件时时,在viewDidLoad函数里,viewController的控 件才能使用。在viewcontroller的构造函数里,nib里的控件都还没有完成链接构造呢。 iPhone程序崩溃不要着急。可以结合使用C*****ole和objc_exception_throw可以快速定位根源所在。在CFNetwork 中,有时候使用CFWriteStreamWrite方法写数据时,会导致该现成被长久block住。
- 原因:在 CFWriteStream不能接受数据时,写数据了。具体解决办法:在CFSriteStream收到异步的 kCFStreamEventCanAcceptBytes通知时,再开始写数据。此时可避免CFWriteStreamWrite导致线程被block 的情形。使用Eavesdrop 抓取网络数据包。
- 在Iphone上有两种读取图片数据的简单方法: UIImageJPEGRepresentation和UIImagePNGRepresentation. UIImageJPEGRepresentation函数需要两个参数:图片的引用和压缩系数.而UIImagePNGRepresentation只需 要图片引用作为参数.通过在实际使用过程中,比较发现: UIImagePNGRepresentation(UIImage* image) 要比UIImageJPEGRepresentation(UIImage* image, 1.0) 返回的图片数据量大很多.譬如,同样是读取摄像头拍摄的同样景色的照片, UIImagePNGRepresentation()返回的数据量大小为199K ,而 UIImageJPEGRepresentation(UIImage* image, 1.0)返回的数据量大小只为140KB,比前者少了50多KB.如果对图片的清晰度要求不高,还可以通过设置 UIImageJPEGRepresentation函数的第二个参数,大幅度降低图片数据量.譬如,刚才拍摄的图片, 通过调用UIImageJPEGRepresentation(UIImage* image, 1.0)读取数据时,返回的数据大小为140KB,但更改压缩系数后,通过调用UIImageJPEGRepresentation(UIImage* image, 0.5)读取数据时,返回的数据大小只有11KB多,大大压缩了图片的数据量 ,而且从视角角度看,图片的质量并没有明显的降低.因此,在读取图片数据内容时,建议优先使用UIImageJPEGRepresentation,并可 根据自己的实际使用场景,设置压缩系数,进一步降低图片数据量大小.
- 原文链接:
http://mobile.51cto.com/iphone-390113.htm
十、Objective-C特有语法:内存管理总结 - ゴルツの惠斌纳閣下
一、 基本原理
1. 什么是内存管理
- 移动设备的内存极其有限,每个app所能占用的内存是有限制的
- 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
- 管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效
2. 对象的基本结构
- 每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
- 每个OC对象内部专门有4个字节的存储空间来存储引用计数器
3. 引用计数器的作用
- 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
- 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
4. 引用计数器的操作
1.方法的基本使用
1> retain :计数器+1,会返回对象本身
2> release :计数器-1,没有返回值
3> retainCount :获取当前的计数器
4> dealloc
* 当一个对象要被回收的时候,就会调用
* 一定要调用[super dealloc],这句调用要放在最后面
2.概念
1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
3> 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错
3.原则
1>.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
2>.你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)
3>.谁retain,谁release
4>.谁alloc,谁release
5. 对象的销毁
- 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
- 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
- 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
- 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
- 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
- 不要直接调用dealloc方法
6.内存管理代码规范
1.只要调用了alloc,必须有release(autorelease)
对象不是通过alloc产生的,就不需要release
2.set方法的代码规范
1> 基本数据类型:直接复制 - (void)setAge:(int)age {_age = age; } 2> OC对象类型 - (void)setCar:(Car *)car {// 1.先判断是不是新传进来对象if ( car != _car ){// 2.对旧对象做一次release[_car release]; // 3.对新对象做一次retain_car = [car retain];} }
3.dealloc方法的代码规范
1> 一定要[super dealloc],而且放到最后面2> 对self(当前)所拥有的其他对象做一次release - (void)dealloc {[_car release];[super dealloc]; }
二、 Xcode的设置
1. 取消ARC
要想手动调用retain、release等方法,在创建项目的时候不要勾选ARC
2. 开启僵尸对象监控
默认情况下,Xcode是不会管僵尸对象的,使用一块被释放的内存也不会报错。为了方便调试,应该开启僵尸对象监控
三、 内存管理原则
1. 原则分析
- QQ堂开房间原理:只要房间还有人在用,就不会解散
- 只要还有人在用某个对象,那么这个对象就不会被回收
- 只要你想用这个对象,就让对象的计数器+1
- 当你不再使用这个对象时,就让对象的计数器-1
2. 谁创建,谁release
- 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
- 换句话说,不是你创建的,就不用你去[auto]release
3. 谁retain,谁release
- 只要你调用了retain,无论这个对象是如何生成的,你都要调用release
4. 总结
- 有始有终,有加就有减
- 曾经让对象的计数器+1,就必须在最后让对象计数器-1
四、 @property参数
retain : 生成的set方法里面,release旧值,retain新值
@property (retain) Book *book;
@property (retain) NSString *name;
1.set方法内存管理相关的参数
* retain : release旧值,retain新值(适用于OC对象类型)
* assign : 直接赋值,不做任何内存管理(默认,适用于非OC对象类型)
* copy : release旧值,copy新值一般用于NSString *)
2.是否要生成set方法
* readwrite : 同时生成setter和getter的声明、实现(默认)
* readonly : 只会生成getter的声明、实现
3.多线程管理
* nonatomic : 性能高 (一般就用这个)
* atomic : 性能低(默认)
4.setter和getter方法的名称
* setter : 决定了set方法的名称,一定要有个冒号 :
* getter : 决定了get方法的名称(一般用在BOOL类型)
// 返回BOOL类型的方法名一般以is开头@property (getter = isRich) BOOL rich;@property (nonatomic, assign, readwrite) int weight;@property (readwrite, assign) int height;@property (nonatomic, assign) int age;@property (retain) NSString *name;
五、 循环引用
1. @class
- 使用场景
对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类,这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错
- 用法概括
#import "Card.h"// @class仅仅是告诉编译器,Card是一个类//@class Card;
- 和#import的区别
* #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息
* 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了
* 在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
2. 循环retain
- 比如A对象retain了B对象,B对象retain了A对象
- 这样会导致A对象和B对象永远无法释放
3. 解决方案
- 当两端互相引用时,应该一端用retain、一端用assign
六、 autorelease
1.autorelease的基本用法
1> 给某个对象发送一条autorelease消息时,会将对象放到一个自动释放池中
2> 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
3> 会返回对象本身
4> 调用完autorelease方法后,对象的计数器不变
2.autorelease的好处
1> 不用再关心对象释放的时间
2> 不用再关心什么时候调用release
3.autorelease的使用注意
1> 占用内存较大的对象不要随便使用autorelease
2> 占用内存较小的对象使用autorelease,没有太大影响
4.错误写法
1> alloc之后调用了autorelease,又调用release @autoreleasepool
{// 1Person *p = [[[Person alloc] init] autorelease]; // 0[p release]; }
2> 连续调用多次autorelease @autoreleasepool {Person *p = [[[[Person alloc] init] autorelease] autorelease]; }
5.自动释放池
1> 在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
2> 当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
6.自动释放池的创建方式
1> iOS 5.0前 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [pool release]; // [pool drain];
2> iOS 5.0 开始 @autoreleasepool { }
void test(){@autoreleasepool{// { 开始代表创建了释放池// autorelease方法会返回对象本身// 调用完autorelease方法后,对象的计数器不变// autorelease会将对象放到一个自动释放池中// 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作Person *p = [[[Person alloc] init] autorelease]; p.age = 10; @autoreleasepool{Person *p2 = [[[Person alloc] init] autorelease];p2.age = 10;}Person *p3 = [[[Person alloc] init] autorelease];} // } 结束代表销毁释放池}
3. 应用实例
- 跟release的对比
以前:
Book *book = [[Book alloc] init];[book release];
现在:
Book *book = [[[Book alloc] init] autorelease];// 不要再调用[book release];
1.系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
2.开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
1> 创建对象时不要直接用类名,一般用self
+ (id)book {return [[[self alloc] init] autorelease];}
3.外界调用[Book book]时,根本不用考虑在什么时候释放返回的Book对象
NSNumber *n = [NSNumber numberWithInt:100];NSString *s = [NSString stringWithFormat:@"jack"];NSString *s2 = @"rose";
十一、OC之内存管理
一 、基本原理
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
// do something
[pool release];
return (0);
}
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i,j;
for (i = 0; i < 100; i++)
{
for (j = 0; j < 10000; j++)
{
[NSString stringWithFormat:@"1234567890"];//
}
}
[pool release];
return 0;
}
运行时通过监控工具可以发现使用的内存在急剧增加,直到pool 销毁时才被释放。
Objective-c程序中可以嵌套创建多个autorelease pool.在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool 来及时释放内存。
int main(int agrc,char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i,j;
for (i = 0; i < 100; i++)
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
for (j = 0; j < 10000; j++)
{
[NSString stringWithFormat:@"1234567890"];//
}
[loopPool release];
}
[pool release];
return 0;
}
十二、iOS retain、strong、weak、assign - ACM_Someone like you
strong与weak是由ARC新引入的对象变量属性
xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的。xcode 4.3(ios5和以上版本)之后就有了ARC,并且开始使用
assign: 用于非指针变量。用于
基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id
如:
@property (nonatomic, assign) int number;
@property (nonatomic, assign) id className; //id必须用 assign
反正记住:前面不需要加 “*” 的就用 assign 吧
retain: 用于指针变量。就是说你定义了一个变量,然后这个变量在程序的运行过程中会被更改,并且影响到其他方法。一般是用于字符串( NSString,NSMutableString ),数组(NSMutableArray,NSArray),字典对象,视图对象(UIView ),控制器对象(UIViewController)等
比如:
@property (nonatomic, retain ) NSString * myString;
@property (nonatomic, retain) UIView * myView;
@property (nonatomic, retain) UIViewController * myViewController;
xcode 4.2不支持ARC,所以会频繁使用retain来修饰,用完释放掉,而xcode4.3支持ARC,可以使用retian,不需要手动释放内存,系统会自动为你完成,如果你在xcode4.3上面开发,retian和strong都是一样的,没区别
strong和weak:
事实上
@property( nonatomic, strong) MyClass *myObject;就是相当于@property(nonatomic, retain) MyClass *myObject; @property(nonatomic, weak )id<RNNewsFeedCellDelegate>delegate; 就是相当于 @property(nonatomic,assign )id<RNNewsFeedCellDelegate>delegate;
现在系统自动生成的属性都是用weak来修饰的,我想应该是xcode 4.2不支持ARC,所以大家都是用retain。现在xcode4.3支持ARC了,于是苹果建议程序员放弃retain,以后都用weak。
weak 就是相当于 assign ,同样可以在xcode4.3开发环境下放弃使用 assign 使用 weak 来代替
unsafe_unretained
unsafe_unretained 就是ios5版本以下的 assign ,也就是 unsafe_unretained , weak, assign 三个都是一个样的。 因为 ios5用的是 weak ,那在ios4.3就用不了,如果你将 weak 修改为 unsafe_unretained ,那就可以用了。说到底就是iOS 5之前的系统用该属性代替 weak 来使用。
copy:这个东西估计是大部分人最不容易搞明白的东西,我也搞不明白。听别人说这个东西基本不用了,效果其实和retain没什么两样,唯一的区别就是 copy只用于NSString而不能用于 NSMutableString 。
不过好像当一个类继承NSObject,那么这个类里面的属性需要使用copy,比如:
#import <Foundation/Foundation.h>
#import <MapKit/MKAnnotation.h>
@interface Annotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@end
反正以后就这么用就是了
反正就记住一点:xcode4.2用retain和assign ;xcode4.3或以上版本用strong与weak 。以前用xcode4.2开发程序的程序员会习惯用retain ,所以代码都是retian的,新手如果从xcode4.3学起的话就用 strong与weak 吧
十三、 Cocos2d-x 的内存管理
既然选择了C++作为游戏开发的语言, 手动的管理内存是难以避免的, 而Cocos2d-x的仿Objctive-C的内存管理方式, 事实上让问题变得更加复杂(仅仅是移植更加方便了), 因为你需要很好的掌握两种语言的内存管理方式, 并且在使用时头脑清晰, 不能混用, 不然等待你的就是无穷的噩梦, 因为引用计数的原因, 问题比起纯粹的C++内存泄漏还要难以定位的多.
这里统一的整理一下目前在Cocos2d-x中的内存管理相关的方法和问题. 为了让思路更加清晰, 我提及的不仅仅是具体在Cocos2d-x中怎么用, 也包含一些为啥在Cocos2d-x中应该这么用.
并且, 因为以下讲到的每个点要讲的详细了, 其实都可以写一整篇文章了, 没法在短时间内详述, 这篇文章也就仅仅作为一个引子而已.
C++的内存管理
C语言的malloc, free
因为C++基本兼容C, 所以C语言的malloc和free也是支持的. 简单的说用法就是malloc后记得free即可.
#include <stdio.h> #include <stdlib.h> const size_t kBufferSize = 16; void test() { char *buffer = (char*)malloc(kBufferSize); memset(buffer, 0, sizeof(char) * kBufferSize); sprintf(buffer, "%s", "Hello World "); printf(buffer); free(buffer); } int main(int, char**) { test(); return 0; }
当然, 还有realloc和calloc这两个平时较少用的内存分配函数, 这里不提了, 在C语言时代, 我们就是用malloc和free解决了我们的内存问题. 重复的对同一段内存进行free是不安全的, 同时, 为了防止free后, 还拿着指针使用(即野指针), 我们一般使用将free后的内存置空.
因为这种操作特别的多, 我们常常会弄一个宏, 比如Cocos2d-x中的
#define CC_SAFE_FREE(p) if(p) { free(p); p = 0; }
C++的new, delete, new[], delete[]
为什么malloc和free在C++时代还不够呢? malloc,free在C++中使用有如下的缺点:
- malloc和free并不知道class的存在, 所以不会调用对象的构造函数和析构函数.
- malloc返回的是void*, 不符合C++向强类型发展的趋势.
于是, BS在C++中增加了new-delete组合以替代malloc, free. 其实new, delete基本上都是用malloc, free实现的更高层函数, 只是增加了构造和析构的调用而已. 所以在平时使用的时候并无区别, 但是因为malloc其实是记录了分配的长度的, 长度以字节为单位, 所以一句malloc对应即可, 不管你是malloc出一个char, 一个int, 还是char的数组或者int的数组, 单位其实都是字节. 而长度一般记录在这个内存块的前几个字节, 在Windows中, 甚至可以使用_msize函数从指针中取出malloc出的内存长度.
而new的时候有两种情况, 可以是一个对象, 也可以是对象的数组, 并且, 没有统一的记录长度. 使得我们需要通过自己记住什么时候new出了一个对象, 什么时候new出了多个对象. 这个问题最近云风还吐槽过. 并且增加了delete[]用于删除new出多个对象的情况. 让问题更加容易隐藏不被发现的是, 当你讲delete用于new[]分配的内存时, 实际相当于删除了第一个对象, 是完全正确的语法, 不会报任何错误, 你只能在运行出现诡异问题的时候再去排查.
这个问题实际是语言设计的决策问题. 在BS这个完美主义者这里, C++语言的设计原则之一就是零负荷规则 — 你不会为你所不使用的部分付出代价. 并且在这里这个规则执行的显然是要比C语言还要严格. 当你只分配一个对象的时候, 为啥要像分配多个对象时一样, 记录一个肯定为1的计数呢? 估计BS是这么想的. 于是我们只好用人工来区分delete和delete[]. 某年某月, 我看到某人说C++是实用主义, 我笑了, 然后我看到C++的一条设计原则是”不一味的追求完美”, 我哭了……
#include <stdio.h> class Test { public: Test() { test_ = __LINE__; printf("Test(): Run Code in %d ", __LINE__); } ~Test() { test_ = __LINE__; printf("~Test(): Run Code in %d ", __LINE__); } void print() { printf("%d ", test_); } private: int test_; }; void test() { Test *temp = new Test; delete temp; Test *temps = new Test[2]; delete []temps; Test *error_delete_temps = new Test[2]; delete error_delete_temps; } int main(int, char **) { test(); return 0; }
上面的代码最后用delete删除了使用new[]分配的内存, 但是编译时即使开启-wall选项, 你也不会看到任何的警告, 运行时也不会报错, 你只会发现析构函数少运行了一次. 实际上就是发生了内存泄漏.
C/C++内存管理的实际使用
上面虚构的例子也就是看看语法, 真实的使用情景就要复杂了很多. 最重要的原则之一就是谁分配谁释放原则. 这个原则即使是用malloc, free也一样需要遵循.
在C语言上的表现形态大概如下:
只管用, 不管分配
典型的例子就是C语言的文件读取API:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
这里的ptr buffer并不由fread内部分配, 而是由外部传进来, fread只管使用这段buffer, 并且假设这段buffer的size 大于等于传进来的size. 通过这样的方式, fread本身逃避了malloc, free的责任, 也就不用关心内存该怎么管理的问题. 好处是fread的实现得到简化, 坏处是外部使用的负担增加了~~~
类似的API还有sprintf, fgets等, 而strcat这种API也算是这种类型的变化.
这种类型的API的一个最大问题在于很多时候不好确定buffer到底该多大, 于是在一些时候, 还有一个复杂的用法, 那就是在第一次传特定参数调用函数时, 函数仅传出需要buffer的大小, 分配了buffer后, 第二次调用才真正实现函数的功能.
这种API虽然外部的使用麻烦, 但是在绝大部分时候, 已经是C语言的最佳API设计方法.
管分配, 也管删除
只管用, 不管分配的API设计方式在仅仅需要一个buffer, 或者简单的结构缓存的时候基本已经够用, 但也不是万能的, 在想要隐藏内部实现时, 往往就没法使用这样的API设计, 比如Windows API中一系列与核心对象创建相关的方法, 如CreateThread, CreateProcess等函数返回的都是一个核心对象的handle, 然后必须要使用CloseHandle来释放, handle具体的对应什么结构, 分配了多少内存, 此时并不由我们关心, 我们只需要保证一个CreateXXX, 就一定要CloseHandle即可.
这种API的设计方式, 还常见于我们熟知的工厂模式, 工厂模式提供Create和Delete的接口, 就要比只提供Create接口, 让外部自己去Delete要好的多, 此时外部不用关心对象的删除方式, 将来需要在创建对象时进行更多操作(比如对对象进行引用计数管理), 外部代码也不需要更改. 我在网上随便一搜, 发现绝大部分关于工厂模式的代码中都没有考虑这个问题, 需要特别注意.
这里看一个我们常见的开源游戏引擎Ogre中的例子: (Ogre1.8.1 OgreFileSystem.h)
/** Specialisation of ArchiveFactory for FileSystem files. */ //class _OgrePrivate FileSystemArchiveFactory : public ArchiveFactory class _OgreExport FileSystemArchiveFactory : public ArchiveFactory { public: virtual ~FileSystemArchiveFactory() {} /// @copydoc FactoryObj::getType const String& getType(void) const; /// @copydoc FactoryObj::createInstance Archive *createInstance( const String& name ) { return OGRE_NEW FileSystemArchive(name, "FileSystem"); } /// @copydoc FactoryObj::destroyInstance void destroyInstance( Archive* arch) { delete arch; } };
这里这个FileSystemArchiveFactory就是典型的例子. 虽然这里的destroyInstance仅仅是一个delete, 但是还是这么设计了接口.
单独的缓存
一般而言, 所有与内存管理相关的C语言API设计都应该使用上面提到的两种方案, 其他的方案基本都是错的(或者有问题的), 但是因为各种考虑, 还是有些很少见的设计, 这里把一些可能出现的设计也列出来.
所谓的单独缓存的设计, 指的是一段代码内部的确需要缓存, 但是不由外部传入, 而是自己直接分配, 只是每次的调用都使用这一份缓存, 不再释放.
比如C语言中对错误字符串的处理:
char * strerror ( int errnum );
strerror这个API的设计咋一看就有蹊跷, 什么样的设计能传入一个整数, 然后返回一个字符串呢? 字符串的存储空间哪里来的? 一个简单的例子就能看出这样设计的问题:
#include <stdio.h> #include <string.h> #include <errno.h> int main () { char *error1 = strerror(EPERM); printf ("%s ", error1); char *error2 = strerror(ENOENT); printf ("%s ", error1); printf ("%s ", error2); return 0; }
此时会输出:
Operation not permitted
No such file or directory
No such file or directory
意思就是说, 当第二次调用strerror的时候, 连error1的内容都变了, 对于不明情况的使用者来说, 这绝对是个很意外的情况. 虽然说, 这个例子比较极端, 在错误处理的时候不太可能出现, 但是这种设计带来的问题都是类似的. 至于为什么获得错误字符串的API要做这样的设计, 我就无从得知了, 可能是从易用性出发更多一些吧.
在另外一些基于效率的考虑时, 也可能会使用这样的设计, 我只在一个对效率有极端要求的情况下, 在真实项目环境中见过这样的设计. 那是在做服务器的时候, 一些辅助函数(对收发数据包的处理)需要大的缓存, 同时操作较为频繁并且效率敏感, 才使用了这样的设计.
一般来说, 这种设计常常导致更多的问题, 让你觉得获得的效率提升付出了不值得的代价. 所以除非万一, 并不推荐使用.
我分配, 你负责释放
这种API的唯一好处就是看起来使用较为简单, 但是在任何情况下, 都不仅仅是容易导致更多问题, 这种设计就是问题本身, 几乎只有错误的代码才会使用这样的设计, 起码有以下几个简单的原因:
- 返回一段buffer的接口, 内部可以使用new, 也可能使用malloc分配, 外部如何决定该使用delete还是free释放, 只能额外说明, 或者看源代码.
- 当API跨内存空间调用时, 就等于错误, 比如当API在动态库中时. 这是100%的错误, 无论是delete还是free, 也不能释放一个在动态库中分配的内存.
正是因为这两个原因, 你几乎不能在任何成熟的代码中看到这样的设计. 但是, 总有人经受不了使用看起来简单的这个诱惑, 比如cocos2d-x中CCFileUtils类的两个接口:
/** @brief Get resource file data @param[in] pszFileName The resource file name which contains the path. @param[in] pszMode The read mode of the file. @param[out] pSize If the file read operation succeeds, it will be the data size, otherwise 0. @return Upon success, a pointer to the data is returned, otherwise NULL. @warning Recall: you are responsible for calling delete[] on any Non-NULL pointer returned. */ unsigned char* getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize); /** @brief Get resource file data from a zip file. @param[in] pszFileName The resource file name which contains the relative path of the zip file. @param[out] pSize If the file read operation succeeds, it will be the data size, otherwise 0. @return Upon success, a pointer to the data is returned, otherwise NULL. @warning Recall: you are responsible for calling delete[] on any Non-NULL pointer returned. */ unsigned char* getFileDataFromZip(const char* pszZipFilePath, const char* pszFileName, unsigned long * pSize);
这种直接返回数据的接口自然比fread这样的接口看起来容易使用很多, 同时还通过一个warning的注释表明了, 应该使用delete[]来释放返回的缓存, 以避免前面提到的第一个问题, 但是, 不管再怎么做, 当你将cocos2d-x编译成动态库, 然后尝试使用上述两个API, 等待你的就是错误. 有兴趣的可以尝试.
这种API还是偶尔能见到, 有的时候可能还不如上面这个例子这么明显, 比如当一个函数要求传入Class** obj参数, 并且返回对象的时候, 其实也已经是一样的效果了.
再次声明, 个人认为这种API本身就是错误, 不推荐在任何时候使用, 看到这么设计的API, 也最好不要在任何时候调用.
C++对内存管理的改进
上述提到的几种涉及内存分配的API设计虽然是C语言时代的, 但是基本也适用于C++. 只是在C++中, 在Class层面, 对内存管理进行了一些新的改进.
有new就delete, 有malloc就free, 听起来很简单, 但是在一些复杂情况下, 还是会成为负担, 让代码变得很难看. 更不要提, 程序员其实也是会犯错误的. C++对此的解决之道之一就是通过构造函数和析构函数.
看下面的例子:
在极其简单的情况下, 我们这样就能保证内存不泄漏:
const int32_t kBufferSize = 32; void Test1() { char *buffer = new char[kBufferSize]; // code here delete[] buffer; }
但是, 这仅限于最简单的情况, 当有可能的错误发生时, 情况就复杂了:
const int32_t kBufferSize = 32; bool Init() { char *buffer = new char[kBufferSize]; bool result = true; // code here if (!result) { delete[] buffer; return false; } char *buffer2 = new buffer[kBufferSize]; // code here if (!result) { delete[] buffer; delete[] buffer2; return false; } delete[] buffer; delete[] buffer2; return true; }
仅仅是两次错误处理, 分配两段内存, 你不小心的写代码, 就很有可能出现错误了. 这不是什么好事情, 更进一步的说, 其实, 我上面这段话还是基于不考虑异常的情况, 当考虑异常发生时, 上述代码就已经是可能发生内存泄漏的代码了. 考虑到异常, 当尝试分配buffer2的内存时, 假如分配失败, 此时会抛出异常(对, 在C++中普通的new分配失败是抛出异常, 而不是返回NULL), 那么实际上此时buffer指向的内存就没有代码负责释放了. 在C++中, 讲buffer放入对象中, 通过析构函数来保证内存的时候, 就不会有这样的问题, 因为C++的设计保证了, 无论以何种方式退出作用域(不管是正常退出还是异常), 临时对象的析构函数都会被调用.
代码大概就会如下面这样:
const int32_t kBufferSize = 32; class Test { public: Test() { buffer_ = new char[kBufferSize]; } ~Test() { delete[] buffer_; } bool process() { // code here return true; } private: char *buffer_; }; bool Init2() { Test test; bool result = test.process(); if (!result) { return false; } Test test2; result = test2.process(); if (!result) { return false; } return true; }
在最简单的情况下, 我们也没有必要构造一个类似上面这种纯粹为了存储Buffer的类, C++ 11中提供了智能指针来完成上述工作:
#include <stdio.h> #include <stdint.h> #include <memory> using namespace std; const int32_t kBufferSize = 32; bool Init2() { unique_ptr<char[]> buffer{new char[kBufferSize]}; bool result = true; // code here if (!result) { return false; } unique_ptr<char[]> buffer2{new char[kBufferSize]}; // code here if (!result) { return false; } return true; }
C++ 11还提供了支持引用计数的智能指针shared_ptr, 但是鉴于智能指针病毒般的传播特性, 建议仅在自己可控制的局部使用, 不在API中出现.
基本上, C++的内存管理虽然是纯手动管理+智能指针结合的较为原始方式, 但是思路还是比较清晰的, 即使牵涉到容器等更加复杂的情况, 其实还是遵循最基本的原则. 当然, 不要跟自动垃圾回收去比使用的方便.
在C++ 11放弃了可选的垃圾回收后, 短期就不要期望C++有垃圾回收机制了, C++这么强烈的期望手动管理内存, 很大程度上是效率的考虑, 即使是现在我看到一些文章说, 好的垃圾回收器效率已经超过一般的手动内存管理了. 然而, 我没有看到任何文章敢说, 好的垃圾回收器, 效率上可以超过好的手动内存管理……
Objective-C的内存管理
Objective-C(以下简称objc)其实已经有可选的垃圾回收功能了, 只不过在iOS上暂时还无法使用(应该是效率上的考虑), 不知道哪天iOS的SDK版本可以使用垃圾回收, 开发app就更简单了.
objc的内存管理自成一派, 将引用技术和内存池直接加入了语言特性.(这么说其实有待商榷, 因为引用计数等是NextStep库的特性, 但是鉴于使用objc就几乎肯定使用NextStep库, 所以这么说也可以接受)
引用计数
简单情况
引用计数策略本身的思路很简单, 每有一个使用到内存块/资源的对象, 就对该内存块/资源的计数加一(retain), 每减少一个就减一(release), 当减到零的时候, 说明没有任何对象用到该内存块/资源, 就释放该内存块/资源.
在实际的使用过程中, 还是有些很dirty的事情, 那就是需要特别注意使用但是不想增加引用计数(assign/weak)的情况. 当两种情况混合的时候, 有的时候会很烦人, 特别的, 当引用计数使用的越多, 出现内存泄漏后, 要查明是哪儿不适当的retain, 或者哪儿有漏掉release就越难.
objc的简单情况下, 可以看成需要retain, release的配对.(类似new, delete的配对)
#import <Foundation/Foundation.h> @interface Obj : NSObject { } - (id) init; @end @implementation Obj - (id) init { self = [super init]; if (self) { NSLog(@"%d", __LINE__); return self; } return nil; } - (void)dealloc { NSLog(@"%d", __LINE__); [super dealloc]; } @end void Test() { id test = [[Obj alloc] init]; [test retain]; [test release]; [test release]; } int main(int argc, const char * argv[]) { @autoreleasepool { Test(); } return 0; }
上面的这种代码其实并没有什么意义, 因为情况太简单了, 需要记住alloc出来的对象引用计数为1即可, retain的调用也太明显了. 但是当有objc容器的时候, 就需要留心了.
容器对引用计数的影响
需要牢记的是objc的容器会隐式的retain对象, 并且接口都是类似AddXXX的形式. 此时光是去统计retain和release已经不管用了, 可以用所有权的思路去分析代码, 也就是把retain看成是宣示所有权, release看成是放弃所有权, 而容器一般都需要对对象拥有所有权.
以常见的NSArray容器为例, 在addObject后, 马上release, 可以看成是对象在当前作用域放弃所有权, 将其移交到容器中, 此时唯一拥有对象的就是容器, 当容器本身释放时, 该所有权也会跟着释放. 见下例:
#import <Foundation/Foundation.h> @interface Obj : NSObject { } - (id) init; @end @implementation Obj - (id) init { self = [super init]; if (self) { NSLog(@"%d: %s", __LINE__, __FUNCTION__); return self; } return nil; } - (void)dealloc { NSLog(@"%d: %s", __LINE__, __FUNCTION__); [super dealloc]; } @end void Test() { id test = [[Obj alloc] init]; // 1 NSLog(@"%d: %s, retain_count:%d", __LINE__, __FUNCTION__, [test retainCount]); id array = [[NSMutableArray alloc] init]; [array addObject:test]; // 2 NSLog(@"%d: %s, retain_count:%d", __LINE__, __FUNCTION__, [test retainCount]); [test release]; // 1 NSLog(@"%d: %s, retain_count:%d", __LINE__, __FUNCTION__, [test retainCount]); [array release]; }
输出:
2013-04-08 01:40:27.580 test_objc_mem[4734:303] 21: -[Obj init]
2013-04-08 01:40:27.582 test_objc_mem[4734:303] 38: Test, retain_count:1
2013-04-08 01:40:27.583 test_objc_mem[4734:303] 42: Test, retain_count:2
2013-04-08 01:40:27.583 test_objc_mem[4734:303] 45: Test, retain_count:1
2013-04-08 01:40:27.583 test_objc_mem[4734:303] 29: -[Obj dealloc]
42行代码输出的retain_count等于2, 就是因为array宣示了一次所有权.
内存池
在objc中, 还常用autoreleasepool来解决前面提到的C/C++语言内存管理的问题. 大部分情况下, objc都不是用C语言那种外部分配内存, 内部使用的方式, 而是用autorelease统一的管理. 见下例:
#import <Foundation/Foundation.h> @interface Obj : NSObject { } - (id) init; @end @implementation Obj - (id) init { self = [super init]; if (self) { NSLog(@"%d: %s", __LINE__, __FUNCTION__); return self; } return nil; } + (id) create { NSLog(@"%d: %s", __LINE__, __FUNCTION__); return [[[Obj alloc] init] autorelease]; } - (void)dealloc { NSLog(@"%d: %s", __LINE__, __FUNCTION__); [super dealloc]; } @end void Test() { @autoreleasepool { id test = [Obj create]; NSLog(@"%d: %s, retain_count:%d", __LINE__, __FUNCTION__, [test retainCount]); id array = [[NSMutableArray alloc] init]; [array addObject:test]; NSLog(@"%d: %s, retain_count:%d", __LINE__, __FUNCTION__, [test retainCount]); [array release]; } NSLog(@"%d: %s", __LINE__, __FUNCTION__); }
注意与上例纯引用计数的区别, 这里的Obj由自己提供的类方法create提供, 创建后直接调用autorelease, 然后外部就没有如上例一样的release了, 虽然create出来的retain count还是等于1.
这里可以把autorelease pool看成一个能宣示所有权的容器, 而autorelease函数就是一个简化的操作, 本质上就是将对象Add到这个容器中. 并且Objc提供了一些语法糖来简单的初始化和释放该容器. 仅此而已.
ARC(Automatic Reference Counting)
在新版的Objc语言中支持了一种叫做ARC的新特性, 可以在编译时期为程序员自动的匹配retain和release, 有类似自动内存管理的方便, 同时不影响内存使用的效率, 程序员本身也不会因为少些了release而导致内存泄漏, 相当强大.
具体关于ARC的内容可以单独成文, 并且本文主要是想讲cocos2d-x的, 而这暂时还和ARC无关, 这里就不多讲了, 可以参考这篇文章: http://longweekendmobile.com/2011/09/07/objc-automatic-reference-counting-in-xcode-explained/
Cocos2d-x的内存管理
要是完全没有接触过Objc, 只是了解C++, 看到cocos2d-x的内存管理设计, 会想说脏话的. 了解objc的话, 起码还能理解cocos2d-x的开发者是尝试在C++中模拟Objc的内存管理方式. 不仅仅是说加引用计数而已, 因为真要在C++中加引用计数的方法有很多种, cocos2d-x用的这种方法, 实在太不原生态了.
简单情况
因为cocos2d-x中牵涉到显示的情况最多, 我也就不拿CCArray这种东西做例子了, 看个CCSprite的例子吧, 用cocos2d-x的XCode template生成的HelloWorld工程中, 删除原来的显示代码, 创建一个Sprite并显示的代码如下:
// part code of applicationDidFinishLaunching in AppDelegate.cpp // create a scene. it's an autorelease object CCScene *scene = HelloWorld::scene(); CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); helloworld->release(); }
这里暂时不管HelloWorld::scene, 先关注CCSprite的创建和使用, 这里使用new创建了CCSprite, 然后使用scene的addChild函数, 添加的到了scene中, 并显示. 一段这样简单的代码, 但是背后的东西却很多, 比如, 为啥我在scene的addChild后, 调用了sprite的release函数呢?
还是可以从引用计数的所有权上说起(这样比较好理解, 虽然你也可以死记哪些时候具体引用计数的次数是几). 当我们用new创建了一个Sprite时, 此时Sprite的引用计数为1, 并且所有权属于helloworld这个指针, 我们在把helloworld用scene的addChild函数添加到scene中后, helloworld的引用计数此时为2, 由helloworld指针和scene共享所有权, 此时, helloworld指针的作用其实已经完了, 我们接下来也不准备使用这个指针, 所有权留着就再也释放不了了, 所以我们用release方法特别释放掉helloworld指针此时的所有权, 这么调用以后, 最后helloworld这个Sprite所有权完全的属于scene.
但是我们这么做有什么好处呢? 好处就是当scene不想要显示helloworld时, 直接removeChild helloworld就可以了, 此时没有对象再拥有helloworld这个sprite, 引用技术为零, 这个sprite会如期的释放掉, 不会导致内存泄漏.
比如说下列代码:
// create a scene. it's an autorelease object CCScene *scene = HelloWorld::scene(); // CCSprite* sprite = CCSprite::create("HelloWorld.png"); CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); helloworld->release(); scene->removeChild(helloworld); }
上面的代码helloworld sprite能正常的析构和释放内存, 假如少了那句release的代码就不行.
容器对引用计数的影响
这个部分是引用计数方法都会碰到的问题, 也就是引用计数到底在什么时候增加, 什么时候减少.
在cocos2d-x中, 我倒是较少会像在objc中手动的retain对象了, 主要的对象主要由CCNode和CCArray等容器管理. 在cocos2d-x中, 以CC开头的, 模拟Objc接口的容器, 都是对引用计数有影响的, 而原生的C++容器, 对cocos2d-x的对象的引用计数都没有影响, 这导致了人们使用方式上的割裂. 大部分用惯了C++的人, 估计都还是偏向使用C++的原生容器, 毕竟C++的原生容器及其配套算法算是C++目前为数不多的亮点了, 比objc原生的容器都要好用, 更别说Cocos2d-x在C++中模拟的那些objc容器了. 但是, 一旦走上这条路就需要非常小心, 要非常明确此时每个对象的所有权是谁.
看下面的代码:
vector<CCSprite*> sprites; for (int i = 0; i < 3; ++i) { CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); sprites.push_back(helloworld); helloworld->release(); scene->removeChild(helloworld); } }
因为C++的容器是对Cocos2d-x的引用计数没有影响的, 所以在上述代码运行后, 虽然vector中保存者sprite的指针, 但是其实都已经是野指针了, 所有的sprite实际已经析构调了. 这种情况相当危险. 把上述代码中的vector改成cocos2d-x中的CCArray就可以解决上面的问题, 因为CCArray是对引用计数有影响的.
见下面的代码:
CCArray *sprites = CCArray::create(); for (int i = 0; i < 3; ++i) { CCSprite *helloworld = new CCSprite; if (helloworld->initWithFile("HelloWorld.png")) { CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the sprite on the center of the screen helloworld->setPosition( ccp(size.width/2, size.height/2) ); // add the sprite as a child to this layer scene->addChild(helloworld, 0); sprites->addObject(helloworld); helloworld->release(); scene->removeChild(helloworld); } }
改动非常小, 仅仅是容器类型从C++原生容器换成了Cocos2d-x从Objc模拟过来的array, 但是这段代码执行后, sprites中的sprite都可以正常的使用, 并且没有问题. 可参考cocos2d-x的源代码ccArray.cpp:
/** Appends an object. Behavior undefined if array doesn't have enough capacity. */ void ccArrayAppendObject(ccArray *arr, CCObject* object) { CCAssert(object != NULL, "Invalid parameter!"); object->retain(); arr->arr[arr->num] = object; arr->num++; }
但是, 假如我就是想用C++原生容器, 不想用CCArray怎么办呢? 需要承担的风险就来了, 有的时候还行, 比如上例, 我只需要去掉helloworld->release
那一行, 并且明白此时所有权已经是属于vector了, 在vector处理完毕后, 再release即可.
而有的时候这就没有那么简单了. 特别是Cocos2d-x因为依赖引用计数, 不仅仅是addChild等容器添加会增加引用计数, 回调的设计(模拟objc中的delegate)也会对引用计数有影响的. 曾经有人在初学Cocos2d-x的时候, 问我cocos2d-x有没有什么设计问题, 有没有啥坑, 我觉得这就是最大的一个.
举个简单的例子, 我真心不喜欢引用计数, 所以全用C++的容器, 写了下面这样的代码: (未编译测试, 纯示例使用)
class Enemy { public: Enemy() {} ~Enemy() {} }; class EnemyManager { public: EnemyManager() {} ~EnemyManager() {} void RemoveEnemies() { for (auto it : enemies_) { delete *it; } } private: vector<Enemy*> enemies_; };
刚开始的时候, 这只是一段和Cocos2d-x完全没有关系的代码, 并且运行良好, 有一天, 我感觉的Enmey其实是个Sprite就方便操作了. 将Enemy改为继承自Sprite, 那么这段代码就没有那么安全了, 因为EnemyManager在完全不知道enemy的引用计数的情况下, 使用delete删除了enmey, 假如此时还有其他地方对该enemy有引用, 就会crash. 虽然表面上看来是想添加一些CCSprite的显示功能, 但是实际上, 一入此门(从CCObject继承过来), 引用计数就已经无处不在, 此时需要把直接的delete改为调用release函数.
内存池
cocos2d-x起始也模拟了objc中的内存池, 但是因为不可能改变语言本身的特性, 那种简单的语法糖语法就没有, 需要的时候, 老实的操作CCPoolManager和CCAutoreleasePool吧. 在通常情况下, cocos2d-x增加的机制使得我们不太需要像在objc中那样使用内存池. 我来解释一下:
在cocos2d-x中, 几乎所有有意义的类都有create函数, 比如Sprite的create函数:
CCSprite* CCSprite::create() { CCSprite *pSprite = new CCSprite(); if (pSprite && pSprite->init()) { pSprite->autorelease(); return pSprite; } CC_SAFE_DELETE(pSprite); return NULL; }
基本只干两个事情, 一个是new和init, 一个就是调用autorelease函数讲sprite本身加入内存池了. 此时讲sprite加入内存池后, sprite的所有权已经属于内存池了, 我们返回的指针其实是没有所有权的. 在create出一个类似对象后, 我们接下来的操作往往是吧这个对象再添加到parent node中(比如上层的scene或layer), 此时由内存池和这个parent node共同拥有这个sprite, 当sprite不需要再显示的时候, 直接通过removeChild将sprite从父节点中移除后, 就回到仅属于内存池的情况了.
在objc中, 要是都是上面的情况, 我们又不手动的清理内存池, 这其实就已经有内存泄漏了, 但是cocos2d-x实际是每帧都帮我们清理内存池的. 也就是说, 每一帧仅仅属于内存池的对象都会被释放. 见下面的代码:
void CCDisplayLinkDirector::mainLoop(void) { if (m_bPurgeDirecotorInNextLoop) { m_bPurgeDirecotorInNextLoop = false; purgeDirector(); } else if (! m_bInvalid) { drawScene(); // release the objects CCPoolManager::sharedPoolManager()->pop(); } }
上面的代码是CCDirector的游戏主循环代码, 主循环干了件非常重要的事情, 那就是pop最上层的autorelease pool, 此时是在release全部仅仅由此内存池所有的对象. 就是依靠这样的原理, 我们可以放心的将对象放在autorelease pool中, 知道在需要的时候, 这个对象就能正确的释放, 同时只要有上层的父节点通过addChild对游戏对象有了所有权以后, 又能正确的保证该对象不会被删除.
小结
本文原来是来自于给公司做的内部培训材料, 因为一开始写的很初略和简单, 一直就没想发布, 最近我在整理老的资料, 所以今天整理了一下, 添加了一些例子, 发布出来了, 可以明显的看到后面的内容虽然更加重要, 但是写的比前面要仓促, 有错误的话, 请各位不吝赐教.
十四、理解 Objective-C 的 ARC
英文原文:Understanding Automatic Reference Counting in Objective-C
自动引用计数(Automatic Reference Counting, ARC)把压在程序员们肩头的管理内存的重担卸除了不少,更不用说让跟踪内存泄漏那样的烦心事也少了很多。不过,虽然ARC很棒,我们仍然不能完全把内存管理这回事儿抛在脑后。 这篇文章将要讨论以下方面的问题,帮助大家快速进入ARC的世界。
|
AlfredCheung
|
发生了什么事? 在ARC出现以前,程序员们只能靠retain/relese/autorelease来确保对象们恰好“坚持”到被需要的那一刻。如果忘了retain,或者多次release某个对象,程序就会发生内存泄漏的问题,甚至直接崩溃。 在Xcode 4.2中,除了语法检查外,Apple的新LLVM编译器还将内存管理的苦差事接了过来,它会检查代码,决定何时释放对象。Apple的文档里是这么定义ARC的: “自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期管理(内存管理)的流程。” ARC使内存管理在大部分时候变得如同小事一桩,但我们仍要在决定自己的类如何管理其它对象的引用时承担一些责任。 那么,让我们正式开始吧…… |
AlfredCheung
|
其它翻译版本(1) |
引用计数: 快速复习手工管理、引用计数式的内存管理在iOS中是这样工作的: 当使用alloc/init(或其它类似方法)创建对象时,随同对象返回的,还有个retainCount,其值为1,表明我们获得了这个对象的所有权。
将对象加入到自动释放池也是类似,对象会一直存在,直到未来的某个时间我们不再需要它,才会被系统回收。
|
AlfredCheung
|
||||||||||||||
其它翻译版本(1) |
ARC的工作原理大多数新的iOS程序员都会在引用计数这问题上遇到理解障碍。ARC则是一个编译前的步骤,它为我们的代码自动加上retain/release/autorelease语句。 ARC并不是垃圾收集,而且,引用计数也没有消失,只是变成自动而已。听起来像是事后追加的这么一个功能,不过,只要我们想一想Objective-C有多少功能是通过对源文件的预处理来实现的,就不会这么想了。 当采用ARC后,代码只要这样写:
从下图(来自Apple官方文档)看起来,好像retain/release的数量快赶上真正有用的代码了。当然,这肯定不是熟手的情况,不过可以看成是对新手的保守估计。这些代码跑起来,要跟踪某个内存问题真的是会搞死人。 |
AlfredCheung
|
在工程中开启ARC如果想开启ARC,只要在工程的Build Settings中设置ARC为YES。在幕后,实际上是设置了-fobj-arc的编译器标识。 ARC施加的新规则如果想用ARC,必须服从一些新规则。 1. 对象的Alloc/Init 创建对象的方法跟以前一样,但你一定不能调用retain/release/autorelease/retainCount。也不能通过selector偷偷地调用它们: 禁止使用@selector(retain)和@selector(release)。 |
AlfredCheung
|
2. dealloc方法 ARC为自动为你调用,一定不能直接调用dealloc。不过,如果你需要释放实例变量以外的资源,还是可以创建自定义的dealloc方法。但在这个方法里,不要调用[super dealloc]。因为ARC会帮你调。 3. 声明的属性 在ARC之前,我们是用@property指令中的assign/retain/copy参数来告诉编译器,如何管理这些属性的内存。用了ARC之后,这些参数就作废了,改用weak/strong这两个参数。 |
AlfredCheung
|
4. C结构中的对象指针 同样禁止使用。文档里建议不要把它们放在结构了,改放到类里去。否则ARC就不认识它们了。可能会出现一些移植上的问题。不过,ARC是可以以文件为单位来关闭的。参考下文的“引入不兼容ARC的代码”。 5. id与void*之间的临时互转 当我们在Core Foundation的C函数和Foundation Kit的Objective-C方法间传递对象时,常常需要进行id和void*两个类型的互转。叫做免费桥接(Toll Free Bridging)。 如果使用ARC,必须在CF对象进入和脱离ARC的控制时,用提示/限定符来告知编译器。限定符有__bridge、__bridge_retain和__bridge_transfer。另外,仍需要用CFRetain和CFRelease来管理Core Foundation的对象。 这一块已经比较高深了,如果你不清楚CF对象是什么,也不需要太烦恼。 |
AlfredCheung
|
6. 以@autoreleasepool代替NSAutoReleasePool 兼容ARC的代码不能再使用NSAutoReleasePool对象,而要改用@autoreleasepool{}块。一个很好的例子:
7. 其它 基于Zone的内存已经没了(在运行时里也没了)。不能再使用NSAllocateObject和NSDeallocateObject。 |
AlfredCheung
|
ARC限定符 - 声明的属性身为程序员,习惯于做出一些决定,例如把某个量声明为变量还是常量、本地还是全局,等等。因此,在这里,我们也要决定某个属性与其它属性的关系。我们用strong/weak来把这一关系告诉编译器。 强引用 强引用是对某对象的引用,并且能阻止它被回收。换句话说,强引用创建了一个所有关系。在ARC之前,我们这么写:
在ARC下,我们需要这么写,以确保当前实例获得被引用对象的所有权(主人不被回收,它也不能被回收)。
弱引用 弱引用是对某对象的引用,但不能阻止它被回收。换句话说,弱引用并不会创建所有关系。在ARC之前,我们这么写:
|
AlfredCheung
|
ARC限定符 - 常规变量上一节是说明如何管理属性。对于常规变量,则有:
来源: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#ownership 注: 我们发现在ARC下,@property中使用“retain”时(而不是“strong”),编译器并不会报错,而且能生成同样结果。但以后可能会变,所以还是用“strong”吧。 |
AlfredCheung
|
移植到ARCXcode 4.2提供了一个移植ARC的工具,还可以帮你将无法自动移植的代码手工转换过去。 1. 打开不兼容ARC的工程,进入Edit > Refactor > Convert to Objective-C ARC。 2. 选择需要转换的构建目标和文件(在后续步骤排除不需要转换的文件) 3. 运行预查,按下一步。 注: 按下一步后,LLVM编译器会开始构建,以便对工程进行分析。如果有错误,是无法进入下一步的。如果是第一次打开低版本Xcode建立的工程,请先执行清理。 |
AlfredCheung
|
其它翻译版本(1) |
4. 检查一下工具建议的修改,并选择是否要排除某个文件。然后按下保存。 注: 如果有文件无法移植,工具也会告诉你。并不是所有文件都需要移植(包括库)。ARC是以文件为基础生效的,可以参考下文,看编译时如何把不需要开启ARC的文件排除在外。 5. 工具会自动设置编译器的标识,开启ARC。可以查看工程的Build Settings确认这一点。 |
AlfredCheung
|
引入不兼容ARC的代码Apple的文档说,“ARC能以文件为基础,与采用手工引用计数的代码进行交互。你可以在部分文件上使用手工引用计数。” 它的意思是说,我们可以让一部分文件用ARC,另一部分文件不用。下面是将文件批量排除在ARC之外的步骤。在我写下这篇文章的时候,还有许多流行的库都没有用ARC,为了解决这个问题,请按照下面的步骤做:
|
AlfredCheung
|
我该用ARC吗?如果你是Objective-C的新手,肯定会欢迎ARC。一开始,有太多的东西要学习,有了ARC就不用担心手工计数的问题。随着你开始接触一些已有的库,就会经历一些痛苦(译者注: 目前的第三方库很多不兼容ARC),但只要习惯了将它们排除在ARC之外,就没什么问题了。 如果你不是新手,在没有ARC的年代已经玩的很high,那么可能会觉得“我干嘛要用它!”对你来说,这可能是正确的答案——就目前而言。因为,大多数流行的库都还没转到ARC下,而且ARC对Core Foundation的支持也不好。使用CF类的时候有一些限制,而且,移植代码的时候,为了让免费桥接生效,还需要加上一大堆限定符。 |
AlfredCheung
|
在我看来,目前ARC已经是可以使用的状态了。不过,除非你对它很熟悉,否则还是先用在新工程里会比较好。虽然ARC兼容iOS 4.0以上,但弱引用只在iOS 5.0以上才支持,所以现在还是不要把所有东西都移植过去(有一些相关的文章,请参考最后的“资源”一节) 至于性能方面,早前有报告指出用ARC之后速度会变快,我想可能是由于减少对自动释放的依赖的缘故。虽然这完全可以通过改进代码、更好地使用retain/release来实现,但我觉得重点在于,ARC总是会自动帮你选择最优的方法。 |
AlfredCheung
|
目前为止,还是有很多令人苦恼的东西被移到ARC,但还不是全部。在长周末后我们会离开一段时间以投入到新的项目,但是这是苹果一个“新的”推荐手段,所以你可以肯定以后的设计决策会继续强大ARC并且让大家离开使用手动的引用计数 ARC资源
|
周荣冰
|
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
十五、IOS 5编程 内存管理 ARC技术概述
Automatic Reference Counting (ARC) 是一个编译期的技术,利用此技术可以简化Objective-C编程在内存管理方面的工作量。
这里我把此技术翻译为自动内存计数器管理技术,下图是使用和不使用此技术的Objective-C代码的区别。
ARC技术是随着XCode4.2一起发布的,在缺省工程模板中,你可以指定你的工程是否支持ARC技术,如果你不指定工程支持ARC技术,在代码中你必须使用管理内存的代码来管理内存。
概述
自动计数(ARC)是一个编译期间工作的能够帮你管理内存的技术,通过它,程序人员可以不需要在内存的retain,释放等方面花费精力。
ARC在编译期间为每个Objective-C指针变量添加合适的retain, release, autorelease等函数,保存每个变量的生存周期控制在合理的范围内,以期实现代码上的自动内存管理。
In order for the compiler to generate correct code, ARC imposes some restrictions on the methods you can use, and on how you use toll-free bridging (see “Toll-Free Bridged Types”); ARC also introduces new lifetime qualifiers for object references and declared properties.
你可以使用编译标记-fobjc-arc来让你的工程支持ARC。ARC在Xcode4.2中引入,在Mac OS X v10.6,v10.7 (64位应用),iOS 4,iOS 5中支持,Xcode4.1中不支持这个技术.
如果你现在的工程不支持ARC技术,你可以通过一个自动转换工具来转换你的工程(工具在Edit->Convert menu),这个工具会自动所有工程中手动管理内存的点转换成合适自动方式的(比如移除retain, release等)。这个工具会转换工程中所有的文件。当然你可以转换单个文件。
ARC提供自动内存管理的功能
ARC使得你不需要再思考何时使用retain,release,autorelease这样的函数来管理内存,它提供了自动评估内存生存期的功能,并且在编译期间自动加入合适的管理内存的方法。编译器也会自动生成dealloc函数。一般情况下,通过ARC技术,你可以不顾传统方式的内存管理方式,但是深入了解传统的内存管理是十分有必要的。
下面是一个person类的一个声明和实现,它使用了ARC技术。
@interface Person : NSObject |
@property (nonatomic, strong) NSString *firstName; |
@property (nonatomic, strong) NSString *lastName; |
@property (nonatomic, strong) NSNumber *yearOfBirth; |
@property (nonatomic, strong) Person *spouse; |
@end |
@implementation Person |
@synthesize firstName, lastName, yearOfBirth, spouse; |
@end |
(有关strong的帮助,请参考 “ARC Introduces New Lifetime Qualifiers.”)
使用ARC,你可以象下面的方式实现contrived函数:
- (void)contrived { |
Person *aPerson = [[Person alloc] init]; |
[aPerson setFirstName:@"William"]; |
[aPerson setLastName:@"Dudney"]; |
[aPerson:setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]]; |
NSLog(@"aPerson: %@", aPerson); |
} |
ARC管理内存,所以这里你不用担心aPerson和NSNumber的临时变量会造成内存泄漏。
你还可以象下面的方式来实现Person类中的takeLastNameFrom:方法,
- (void)takeLastNameFrom:(Person *)person { |
NSString *oldLastname = [self lastName]; |
[self setLastName:[person lastName]]; |
NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]); |
} |
ARC可以保证在NSLog调用的时候,oldLastname还存在于内存中。
ARC中的新规则
为了ARC能顺利工作,特增加如下规则,这些规则可能是为了更健壮的内存管理,也有可能为了更好的使用体验,也有可能是简化代码的编写,不论如何,请不要违反下面的规则,如果违反,将会得到一个编译期错误。
-
下面的这些函数:
dealloc,
retain
,release
,retainCount
,autorelease。禁止任何形式调用和实现(dealloc可能会被实现),包括使用
@selector(retain)
,@selector(release)
等的隐含调用。你可能会实现一个和内存管理没有关系的dealloc,譬如只是为了调用
[systemClassInstance setDelegate:nil]
,但是请不要调用[super dealloc]
,因为编译器会自动处理这些事情。 -
你不可以使用
NSAllocateObject
或者
NSDeallocateObject
.使用alloc申请一块内存后,其他的都可以交给运行期的自动管理了。
- 不能在C语言中的结构中使用Objective-c中的类的指针。
请使用类类管理数据。
-
不能使用NSAutoreleasePool
.作为替代,@autoreleasepool被引入,你可以使用这个效率更高的关键词。
-
不能使用memory zones.
NSZone不再需要
—本来这个类已经被现代Objective-c废弃。
ARC在函数和便利变量命名上也有一些新的规定
-
禁止以new开头的属性变量命名。
ARC Introduces New Lifetime Qualifiers
ARC introduces several new lifetime qualifiers for objects, and zeroing weak references. A weak reference does not extend the lifetime of the object it points to. A zeroing weak reference automatically becomes nil
if the object it points to is deallocated.
You should take advantage of these qualifiers to manage the object graphs in your program. In particular, ARC does not guard against strong reference cycles (previously known as retain cycles—see “Practical Memory Management”). Judicious use of weak relationships will help to ensure you don’t create cycles.
属性变量修饰符
weak和strong两个修饰符是新引进的,使用例子如下:
// 下面的作用和: @property(retain) MyClass *myObject;相同 |
@property(strong) MyClass *myObject; |
// 下面的作用和"@property(assign) MyClass *myObject;"相识 |
// 不同的地方在于,如果MyClass的实例析构后,这个属性变量的值变成nil,而不是一个野指针, |
|
@property(weak) MyClass *myObject; |
Variable Qualifiers
You use the following lifetime qualifiers for variables just like you would, say, const
.
__strong |
__weak |
__unsafe_unretained |
__autoreleasing |
__strong
is the default. __weak
specifies a zeroing weak reference to an object. __unsafe_unretained
specifies weak reference to an object that is not zeroing—if the object it references is deallocated, the pointer is left dangling. You use__autoreleasing
to denote arguments that are passed by reference (id *
) and are autoreleased on return.
Take care when using __weak
variables on the stack. Consider the following example:
NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; |
NSLog(@"string: %@", string); |
Although string
is used after the initial assignment, there is no other strong reference to the string object at the time of assignment; it is therefore immediately deallocated. The log statement shows that string
has a null value.
You also need to take care with objects passed by reference. The following code will work:
NSError *error = nil; |
BOOL OK = [myObject performOperationWithError:&error]; |
if (!OK) { |
// Report the error. |
// ... |
However, the error declaration is implicitly:
NSError * __strong e = nil; |
and the method declaration would typically be:
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error; |
The compiler therefore rewrites the code:
NSError __strong *error = nil; |
NSError __autoreleasing *tmp = error; |
BOOL OK = [myObject performOperationWithError:&tmp]; |
error = tmp; |
if (!OK) { |
// Report the error. |
// ... |
The mismatch between the local variable declaration (__strong
) and the parameter (__autoreleasing
) causes the compiler to create the temporary variable. You can get the original pointer by declaring the parameter id __strong *
when you take the address of a __strong
variable. Alternatively you can declare the variable as __autoreleasing
.
Use Lifetime Qualifiers to Avoid Strong Reference Cycles
You can use lifetime qualifiers to avoid strong reference cycles. For example, typically if you have a graph of objects arranged in a parent-child hierarchy and parents need to refer to their children and vice versa, then you make the parent-to-child relationship strong and the child-to-parent relationship weak. Other situations may be more subtle, particularly when they involve block objects.
In manual reference counting mode, __block id x;
has the effect of not retaining x
. In ARC mode, __block id x;
defaults to retaining x
(just like all other values). To get the manual reference counting mode behavior under ARC, you could use__unsafe_unretained __block id x;
. As the name __unsafe_unretained
implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use __weak
(if you don’t need to support iOS 4 or OS X v10.6), or set the __block
value to nil
to break the retain cycle.
The following code fragment illustrates this issue using a pattern that is sometimes used in manual reference counting.
MyViewController *myController = [[MyViewController alloc] init…]; |
// ... |
myController.completionHandler = ^(NSInteger result) { |
[myController dismissViewControllerAnimated:YES completion:nil]; |
}; |
[self presentViewController:myController animated:YES completion:^{ |
[myController release]; |
}]; |
As described, instead, you can use a __block
qualifier and set the myController
variable to nil
in the completion handler:
__block MyViewController *myController = [[MyViewController alloc] init…]; |
// ... |
myController.completionHandler = ^(NSInteger result) { |
[myController dismissViewControllerAnimated:YES completion:nil]; |
myController = nil; |
}; |
Alternatively, you can use a temporary __weak
variable. The following example illustrates a simple implementation:
MyViewController *myController = [[MyViewController alloc] init…]; |
// ... |
__weak MyViewController *weakMyViewController = myController; |
myController.completionHandler = ^(NSInteger result) { |
[weakMyViewController dismissViewControllerAnimated:YES completion:nil]; |
}; |
For non-trivial cycles, however, you should use:
MyViewController *myController = [[MyViewController alloc] init…]; |
// ... |
__weak MyViewController *weakMyController = myController; |
myController.completionHandler = ^(NSInteger result) { |
MyViewController *strongMyController = weakMyController; |
if (strongMyController) { |
// ... |
[strongMyController dismissViewControllerAnimated:YES completion:nil]; |
// ... |
} |
else { |
// Probably nothing... |
} |
}; |
In some cases you can use __unsafe_unretained
if the class isn’t __weak
compatible. This can, however, become impractical for nontrivial cycles because it can be hard or impossible to validate that the __unsafe_unretained
pointer is still valid and still points to the same object in question.
自动释放池
使用ARC,你不能使用NSAutoReleasePool类来管理自动释放池了,作为替代,ARC使用一个新的语法结构:
@autoreleasepool { |
// Code, such as a loop that creates a large number of temporary objects. |
} |
编译器根据工程是否使用ARC,来决定这个语法结构最终呈现方式,这个语法结构使用了一种比NSAutoReleasePool
更高效的方式。
Outlets
The patterns for declaring outlets in iOS and OS X change with ARC and become consistent across both platforms. The pattern you should typically adopt is: outlets should be weak
, except for those from File’s Owner to top-level objects in a nib file (or a storyboard scene) which should be strong
.
Outlets that you create should will therefore generally be weak
by default:
-
Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.
-
The
strong
outlets are frequently specified by framework classes (for example,UIViewController
’sview
outlet, orNSWindowController
’swindow
outlet).
For example:
@interface MyFilesOwnerClass : SuperClass |
@property (weak) IBOutlet MyView *viewContainerSubview; |
@property (strong) IBOutlet MyOtherClass *topLevelObject; |
@end |
In cases where you cannot create a weak reference to an instance of a particular class (such as NSTextView
), you should use assign
rather than weak
:
@property (assign) IBOutlet NSTextView *textView; |
This pattern extends to references from a container view to its subviews where you have to consider the internal consistency of your object graph. For example, in the case of a table view cell, outlets to specific subviews should again typically beweak
. If a table view contains an image view and a text view, then these remain valid so long as they are subviews of the table view cell itself.
Outlets should be changed to strong
when the outlet should be considered to own the referenced object:
-
As indicated previously, this often the case with File’s Owner: top level objects in a nib file are frequently considered to be owned by the File’s Owner.
-
You may in some situations need an object from a nib file to exist outside of its original container. For example, you might have an outlet for a view that can be temporarily removed from its initial view hierarchy and must therefore be maintained independently.
其他的新功能
使用ARC技术,可以使得在栈上分配的指针隐式的初始化为nil,比如
- (void)myMethod { |
NSString *name; |
NSLog(@"name: %@", name); |
} |
上面的代码会Log出来一个null,不会象不使用ARC技术的时候使得程序崩溃。
十六、Xcode如何查看内存中的数据
在 debug 模式下如何在断点处,查看字符指针变量内存中的值,像vs2008的调试工具一样的内存查看器,现在只能查看第一个内存中的值可以在输出窗口采用gdb命令:x /nfu <addr>
n表示要显示的内存单元的个数
-----------------------------------------
f表示显示方式, 可取如下值:
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式显示变量
i 指令地址格式
c 按字符格式显示变量
f 按浮点数格式显示变量
-----------------------------------------
u表示一个地址单元的长度:
b表示单字节
h表示双字节
w表示四字节
g表示八字节
-------------------------------------------
例如x/16xb self
会显示self指针地址内容,16个字节,16进制
-------------------------------------------
-------------------------------------------
用 Xcode Debug 时可以用以下方法查看全局变量:
Objective-C 直接在console(控制台-gdb)输入 po+变量名
开发程序时,加了断点进行debug但发现不知到怎样查看变量的内容。用惯eclipse了。看到控制台上显示GDB,就查了下GDB的命令,方便以后使用
clear FILENAME:NUM 删除断点。
continue 继续执行直到下一个断点,也可以写做cont
help NAME 帮助
break NUM 在某行设置断点
kill 终止被调试的程序
print-object 显示对象的内容,也可以写做po
whatis 查看对象的数据类型
next 向前执行一行代码
step 进入一个方法
finish 跳出一个方法
以上命令可以在xcode的控制台进行输入
在GDB窗口中使用po就可以查看变量.(po = print object)
1)查看String 或其它变量。
po 变量名
2)查看某个Property。比如要查看item变量的name属性。
po [item name] 注意,po item.name是不工作的。
3)查看数组
po [myArray objectAtIndex:index]
/******************************************************我是分割线***********************************************/
在Xcode中,Debug时,不能像eclipse ,或VS那些集成开发那样,能直接查看变量的值。那怎么在调试的时候查看XCode的变量呢?
有一些方法的。
1、新建一个Single View App
在viewDidLoad里添加些代码:
- (void)viewDidLoad{ [super viewDidLoad]; NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"value1",@"key1", @"28", @"age",@"rongfzh",@"name" ,nil]; UILabel *label = [[UILabel alloc] init]; label.frame = CGRectMake(20, 40, 250, 60); label.text = [dic objectForKey:@"name"]; [self.view addSubview:label];}
在最后一行打上断点。
2、"po" : print object 命令 打印出对象。
Command+R调试运行,在 Debug Console 上lldb上输入po dic回车,显示如下:
这就把词典内容打印出来了。
再打印label试试。
(lldb) po label
(UILabel *) $3 = 0x06a8bdd0 <UILabel: 0x6a8bdd0; frame = (20 40; 250 60); text = 'rongfzh'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x6a8be90>>
label的信息也打印出来了。
3、print命令
print (char*)[[dic description] cString]
(char *) $4 = 0x06d79760 "{ age = 28; key1 = value1; name = rongfzh; }"
打印对象的retainCount,但对象被回收
(lldb) print (int)[label retainCount]
(int) $2 = 1
/*******************************************************************************************************************/
1)查看String 或其它变量。
十七、Objective-C中的@property和@synthesize用法
Objective-C语言关键词,与@synthesize配对使用。
功能:让编译好器自动编写一个与数据成员同名的方法声明来省去读写方法的声明。
如:
1、在头文件中:
@property int count;
等效于在头文件中声明2个方法:
- (int)count;
-(void)setCount:(int)newCount;
2、实现文件(.m)中
@synthesize count;
等效于在实现文件(.m)中实现2个方法。
- (int)count
{
return count;
}
-(void)setCount:(int)newCount
{
count = newCount;
}
以上等效的函数部分由编译器自动帮开发者填充完成,简化了编码输入工作量。
1、 @property(nonatomic,retain)test* thetest;
setter分析
1、retain
代码说明
如果只是@property NSString*str;
则通过@synthesize自动生成的setter代码为:
-(void)setStr:(NSString*)value{
str=value;
}
如果是@property(retain)NSString*str;
则自动的setter内容为:
-(void)setStr:(NSString*)v{
if(v!=str){
[str release];
str=[v retain];
}
}
答:assign用于简单数据类型,如NSInteger,double,bool,retain 和copy用户对象,copy用于当 a指向一个对象,b也想指向同样的对象的时候,如果用assign,a如果释放,再调用b会crash,如果用copy 的方式,a和b各自有自己的内存,就可以解决这个问题。retain 会使计数器加一,也可以解决assign的问题。另外:atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:
if (property != newValue) {
[property release];
property = [newValue retain];
}
十八、了解iOS 6开发第三篇:内存管理 - 手动引用计数与自动引用计数
本文不会举例介绍MRC,如果不了解MRC,最好先简单了解一下,再回来读这篇文章。
从iOS 5开始,iOS开发就可以使用自动引用计数(Automatic Reference Counting)了。一直想写一篇详细的笔记,来整理这方面的信息,但一直拖到现在。这篇文章是
WWDC 2012 Session 406 - Adopting Automatic Reference Counting的笔记,如果对LLVM感兴趣,可以去llvm.org深入了解一下。
关于自动引用计数(Automatic Reference Counting, aka ARC)
很多工程师都是最近一、两年才开始接触iOS开发的。但大多数工程师都接触过Java/C#这样的静态编译语言,或者python/ruby这样的动态脚本语言。这些“现代”语言的都提供Garbage Collection的机制,由GC来负责内存的回收。我先强调一点:ARC是编译器提供的机制,而不是GC这种运行时提供的机制。要了解ARC,必须明白什么是引用计数,必须从MRC开始。此外,你会遇到很多旧的开源代码和静态库(比如广告平台)。这些很可能不支持ARC,所以还是要理解MRC。
关于手动引用计数(Manully Reference Counting, aka MRC)
Objective-C的MRC,简单来说就是一句话:谁创建,谁释放。复杂一点的解释如下:
- 所有由alloc、copy(mutablecopy)、retain、new创建的object,必须手动release(或autorelease)
- 反之,则不需要也不可以
- autorelease不是自动释放,是AutoreleasePool的实例,在runloop中被”稍后“释放。
MRC有什么问题
常犯的错误:
- Crash! Reject! 内存管理是导致app crash的最主要原因。也是App Store审核时拒审的最多的原因;
- 规则简单,但细节太多,依赖于工程师的清醒头脑和良好习惯,依赖于命名规范
- Dangling Pointer,delegate需要手动设置为nil
- 触发NSError、或者有复杂的条件分支逻辑时,忘记释放内存
需要使用一堆工具,帮助你检测代码:
- Instruments: Allocations, Leaks, Zombies
- Static Analyzer
- Heap
- ObjectAlloc
- vmmap
- Debugger
工程师需要做的事情:
- 理解MRC,写正确的代码
- 正确的命名规范
- 使用五花八门的辅助工具,来保证代码没问题
- 保持清醒的头脑,不犯低级错误
这不是工程师所擅长的事情,这个工作应该由编译器来做才对—LLVM 3.1
什么是ARC
- Objective-C对象的自动管理机制
- 由LLVM编译器来保证
- 与MRC完全兼容
- 引入新的运行时特性:弱指针、性能优化
ARC不是
- 新的运行时模型
- 自动malloc/free, CF等等(不负责纯C代码的内存管理)
- 不是GC。不扫描堆、不暂停进程运行、没有不确定的内存释放
ARC如何工作
- 编译器在编译期,帮你动态添加retain/release/autorelease
- 返回同样结果的不同API,在内存方面,没有区别(把alloc/init模式与convenient method当成一样就可以了)
ARC有什么好处
- 不用写retain/release/autorelease这样的代码,甚至连dealloc方法都不需要(前提是类不需要管理C指针)
- 更好的性能,ARC智能地在”适当“的地方插入retain/release
- Block just work
- Less code, less bug
- 少浪费脑细胞和脑时钟周期
如何打开ARC
- Xcode的Project Setting中,设置Objective-C Automatic Reference Counting为YES
- Xcode 4.2+,新的Project默认为ARC
为了实现ARC,LLVM编译器必须保证4条规则
- 不能调用或实现跟引用计数相关的方法(retain/release/autorelease/retaincount等),否则产生编译错误
- C的结构体里面,不能有Object指针;如果必须使用,那么可以用Objective-C的类代替
- 编译器必须清楚void*是否被retained。引入新的API来转换Objective-C和Core Foundation类型的对象
- 编译器必须清楚自动释放的对象,因此AutoreleasePool不能是对象,而只是语义上的符号。@autoreleasepool directive甚至可能在MRC中使用
如何理解ARC
- 从对象的从属关系角度考虑:强引用(retain)保证对象的存在,或没有强引用,则对象被自动销毁
- 想清楚程序的对象图
- 不要再考虑retain, release, autorelease了
弱引用(必须在iOS 5中使用)
- 安全的”弱指针“,引用的对象被销毁时,自动变成nil;避免了Dangling Pointer
- 在property中使用weak关键字声明,ivar中使用_weak声明
循环引用
- ARC依然存在循环引用的可能
- 手动设置某一个引用为nil,破坏循环引用
- 使用弱引用来避免
性能
- 与MRC没有实质的性能区别,甚至更好(有时候好得多),内存峰值比MRC低
- 没有GC的开销;没有延迟的销毁,没有进程暂停,没有不能确定的释放
ARC的优点
- 更容易理解
- 写更少的代码,更少的bug
- 更容易维护,甚至更好的性能
- 安全、稳定
iOS的什么版本才能支持ARC
- iOS 6发布后,以iOS 5.0作为Deployment Target完全没问题。所以都可以使用ARC。如果你必须支持5.0以下的设备,那么看后两条。
- ARC由LLVM 3.1保证,LLVM 3.1在安装Xcode 4.2自动安装;也就是说只要Xcode 4.2以上的版本,可以设置的Deployment Target的最低版本,都支持ARC。笔者在4.0+的app中使用ARC。
- 弱引用(Weak Reference)必须在iOS 5以上的设备才支持
总结
笔者其实觉得MRC很容易掌握,profile的工具也容易掌握。但ARC实实在在的从安全、可维护性、代码量、性能上有全面的提升。所以除非维护旧的代码,笔者都建议使用ARC。不用担心app依赖第三方的MRC代码,可以通过编译器设置文件的编译属性来混合使用ARC和MRC。
如果想更多地了解LLVM是如何智能地保证ARC,请阅读文章头部引用的两篇文章,里面有详细的解释和代码例子。
本博客的所有文章都由我的Evernote笔记慢慢积累并整理而来。如果您喜欢我的文章,并希望尝试使用免费的Evernote作笔记,请通过我的推广码注册支持我。