您的位置:首页 > 移动开发 > IOS开发

iOS之内存管理

2015-07-01 23:46 671 查看
1 配对原则

alloc – release

new – release

retain - release

copy – release

2 new和alloc-init的区别

(1)先看看实现源码

+new
{
id newObject =(*_alloc)((Class)self, 0);
Class metaClass = self->isa;
if (class_getVersion(metaClass)> 1)
{
return [newObject init];
}
else
{
return newObject;
}
}

+alloc
{
return(*_zoneAlloc)((Class)self, 0, malloc_default_zone());
}

-init
{
return self;
}


通过源码我们发现,[className new]基本等同于[[classNamealloc] init];

区别只在于alloc分配内存的时候使用了zone.

这个zone是个什么呢?

它是给对象分配内存的时候,把关联的对象分配到一个相邻的内存区域内,以便于调用时消耗很少的代价,提升了程序处理速度。

(2)为什么不推荐使用new

因为若用了new,则初始化方法只能是init。这样,假如你想调用initWithFrame,initWithVideoId, initWithPlayItem是无法做到的。另一个原因是习惯和风格的问题。

3 NSString 对象为何用copy

(1)NSString在Objective-C中是一种非常特殊的对象,其引用系数不受引用计数规则的控制。NSString对象不管是alloc、retain还是release,其引用计数都是-1。看下面一段代码:

NSString *aStr = [[NSString alloc] initWithString:@"abc"];
NSLog(@"aStr address = %p", aStr);
NSLog(@"aStr retainCount after alloc = %d", [aStr retainCount]);

NSString *bStr = [aStr copy];
NSLog(@"aStr retainCount after copy = %d", [aStr retainCount]);
NSLog(@"bStr address = %p", bStr);
NSLog(@"bStr retainCount = %d", [bStr retainCount]);

NSString *cStr = [aStr retain];
NSLog(@"aStr retainCount after retain = %d", [aStr retainCount]);
NSLog(@"cStr address = %p", cStr);
NSLog(@"cStr retainCount = %d", [cStr retainCount]);

[aStr release];
NSLog(@"aStrretainCount after release = %d", [aStr retainCount]);

输出结果:
2013-10-15 21:51:11.916Test[5575:a0b] aStr address = 0x3548
2013-10-15 21:51:11.918Test[5575:a0b] aStr retainCount after alloc = -1
2013-10-15 21:51:11.919Test[5575:a0b] aStr retainCount after copy = -1
2013-10-15 21:51:11.919Test[5575:a0b] bStr address = 0x3548
2013-10-15 21:51:11.919Test[5575:a0b] bStr retainCount = -1
2013-10-15 21:51:11.920Test[5575:a0b] aStr retainCount after retain = -1
2013-10-15 21:51:11.920Test[5575:a0b] cStr address = 0x3548
2013-10-15 21:51:11.921Test[5575:a0b] cStr retainCount = -1
2013-10-15 21:51:11.921 Test[5575:a0b] aStrretainCount after release = -1


(2)对于NSMutableString,看下面一段代码

NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"abc"];
NSLog(@"str1 address = %p", str1);
NSLog(@"str1 retaincount after alloc = %d", [str1 retainCount]);

NSString *str2 = [str1 copy];
NSLog(@"str1retaincount after copy = %d", [str1 retainCount]);
NSLog(@"str2 address = %p", str2);
NSLog(@"str2 retaincount = %d", [str2 retainCount]);

NSString *str3 = [str1 retain];
NSLog(@"str1 retaincount after retain = %d", [str1 retainCount]);
NSLog(@"str3 address = %p", str3);
NSLog(@"str3 retaincount = %d", [str3 retainCount]);

[str1 setString:@"edf"];
NSLog(@"str1 address = %p", str1);
NSLog(@"after str1 changed, str2 = %@", str2);
NSLog(@"afterstr1 changed, str3 = %@", str3);

运行结果:
2013-10-15 22:14:51.735Test[5794:a0b] str1 address = 0x8c46510
2013-10-15 22:14:51.736Test[5794:a0b] str1 retaincount after alloc = 1
2013-10-15 22:14:51.737Test[5794:a0b] str1 retaincount after copy = 1
2013-10-15 22:14:51.737Test[5794:a0b] str2 address = 0x8c47680
2013-10-15 22:14:51.738Test[5794:a0b] str2 retaincount = 1
2013-10-15 22:14:51.738Test[5794:a0b] str1 retaincount after retain = 2
2013-10-15 22:14:51.739Test[5794:a0b] str3 address = 0x8c46510
2013-10-15 22:14:51.739Test[5794:a0b] str3 retaincount = 2
2013-10-15 22:14:51.739Test[5794:a0b] str1 address = 0x8c46510
2013-10-15 22:14:51.740Test[5794:a0b] after str1 changed, str2 = abc
2013-10-15 22:14:51.740 Test[5794:a0b] after str1changed, str3 = edf


从(1)中的结果可以看出,对于NSString来说,用alloc和用retain都是指向同一块内存,区别不大。

但从(2)中来看,对于NSMutableString来说,alloc实际上就是开辟了一块新内存,再把内容复制进来,而retain内存不变引用计数+1。如果NSMutableString中的内容被改变了的话,用retain之后的str3内容也被改变。这是写程序过程中所不想要的结果。所以这种情况下,用copy比较安全。

另外,因为苹果的官方SDK,都把NSString属性声明为copy,比如UILabel中的两个属性:

@property(nonatomic,copy) NSString *text; //default
is nil

@property(nonatomic,copy) NSAttributedString *attributedText NS_AVAILABLE_IOS(6_0); //
default is nil

所以规范上在声明NSString或其相关子类属性变量时,都声明为copy。

4 强引用与弱引用(retain, assign,strong, weak)

retain为强引用,会导致引用计数+1。想象一下一只小狗被一根绳子拴着,强引用一次就相当于多了一条绳子。只有在所有绳子断开之后,小狗才会跑开。

assign为弱引用,不会引起引用计数的变化。想象一下一只小狗被一根绳子拴着,一个或一群小孩子指着小狗说:快看,这里有条小狗!不管有多少个小孩子用手指指着那条狗,只要那条绳子断开,小狗就会跑掉。

weak和strong只有你打开ARC时才会被要求使用。这时你无法使用retain/release/autorelease。strong等价于retain。weak大体上相当于assign,区别在于weak比assign多了一个功能,当对象消失后自动把指针变成nil,好处不言而喻。

assign除了了可以用来修饰基本类型,如布尔型,整型,浮点型外,一些对象也要使用assign。举两个最常见的例子。

例一:delegate假如用retain会有什么后果?

在AController.m中的某个方法内有如下代码:

_bView = [[BView alloc]initWithFrame:frame];

_bView.delegate = self;

然后在dealloc方法中释放_bView:

_bView.delegate = nil;

release_nil(_bView);

假如这里delegate用的是retain, 那么AController对象的引用计数被_bView retain了一次 ,这样只有在_bView释放时AController对象才有可能被释放。而_bView的释放又依赖于AController的释放,这就造成了循环引用。AController对象和_bView永远得不到释放。

结论:delegate一定要用assign,不能用retain。

例二:AController中有个BController对象

BController *bController = [[BControlleralloc] init];
bController.parentController = self;
[self presentViewController:bController animated:YES];
[bController release];


与delegate同样的道理,这里的parentController只能用assign。

理由是,这里bController被alloc了一下,引用计数是1,被present之后,引用计数增加若干,假设增加了3(反正肯定不止增加1)。这时bController的引用计数为1+3=4。然后release一下,减一变为3。这个3要什么时候被减去变为0呢?推测是在AController被销毁的时候,通过[super dealloc];这句方法内部调用一系列的方法 来释放bController。这里假如parentController误声明为retain,则bController持有AController对象并使AController对象的引用计数+1,则AController中的dealloc永远不会被调用,BController中的dealloc永远也不会被调用。

5 ARC

简介:Objective-c中提供了两种内存管理机制MRC(Manual ReferenceCounting)和ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求。ARC是iOS 5推出的新功能,简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。该机制在 iOS 5/ Mac OS X 10.7 开始导入,从Xcode4.2开始可以使用该机能。简单地理解ARC,就是通过指定的语法,让编译器(LLVM
3.0)在编译代码时,自动生成实例的引用计数管理部分代码。

优点:1)在很大程度上消除了手动内存管理的负担,同时省去了追查内存泄露和过度释放对象引起的繁琐操作;2)根据Apple的说法,ARC的效率反倒高。效率为什么会提高了呢?主要是之前通过autorelease释放的东西都是随着runloop在autorelease
pool中一起释放的,而开启了ARC后,很多之前要autorelease的东西直接就通过类似手动管理的方式释放掉了,根本没放到autoreleasepool中,从而提升了效率。

缺点:1)部分流行的开源库还没有转为ARC; 2)网上有人发现,使用ARC时,对于非ARC代码,只需要在编的时候加上-FOBJ-ARC啥的就好了,实际上不是的。因为加上这个代码之后,一旦你的程序中有ARC和非ARC这两个CLASS之间交互的时候,就会莫名的出现内存泄露,而且泄漏得莫名其妙。3)ARC对Core
Foundation支持的不好,仍然需要xxxxRetain, xxxxRelease;

建议:学习iOS开发内存这块是必须要清楚的,最好是刚开始学习写代码时手动管理内存,对iOS管理内存的机制比较清楚了后,再考虑要不要使用ARC(自动引用计数)机制。即使是使用ARC(自动引用计数)也要了解iOS的内存管理机制,如果你不了解iOS管理内存的机制,只会用ARC让系统帮你管理内存,可以说你的知识结构是有缺陷的,在解决一些程序中遇到的bug时会浪费掉大量的时间。所以通过手动管理内存来深刻理解iOS内存管理机制还是很有必要的。

6 iOS平台常见的内存问题

1)过度释放,立马崩溃

2)少释放造成内存泄露,当有较多的泄露时,尤其是在循环中,有可能导致崩溃。

7 用release_nil宏的好处

#define release_nil(x) [(x) release]; (x)= nil


用release_nil宏不会造成过度释放。因为释放完把指针置为nil,即指向0x0这快特殊的内存区。若不小心再度释放,因为是对nil(0x0)进行释放,这是安全的,所以不会导致程序崩溃。

8 Objective-C中的setter和getter方法

setter和getter存取方法可以说是一个类最基本的东西,任何一门面向对象的语言,都有这个概念,如C++、java、Objective-C等等。因为setter和getter是对面向对象语言封装的最基本的支持。

在Objective-C的setter和getter存取方法,在属性声明为assign和retain(copy)时内部实现有所不同。

1)声明为assign的属性

if([self.delegate respondsToSelector:@selector(someMethond)])

这里self.delegate调用的是getter方法,其内部实现为

- (id)delegate
{
return _delegate;
}


otherObject.delegate = self;这里赋值调用的是setter方法,其内部实现为

- (void)setDelegate:(id)delegate
{
_delegate = delegate;
}


2)声明为retain的属性

[self.view addSubview:aSubView];这里的self.view调用的是getter方法,其内部实现为

- (UIView *)view
{
return _view;
}


self.view = newView;这里赋值调用的是setter方法,其内部实现为

- (void)setView:(UIView *)view
{
if(_view != view)
{
[_view release];
_view = [view retain];
}
}


9 项目中几种常见的内存问题

例1:外部传进来的对象要retain(如果是字符串对象则用copy)

- (void) setPlayItem:(PlayItem*)item
{
_playItem = item;
}


上面的item有被释放的可能,所以要加retain:

_playItem = [item retain];

例2:

if(self.playItem)
{
[self.playItem release];
}


这段代码没大问题,但是不够严谨。用productàanalyze工具分析的时候,会被报警告。[self.playItem release];最好改为self.playItem = nil。

例3:

if(self1.playItem)
{
self1.playItem = nil;
}
self1.playItem = newItem;


这里应该直接写成self1.playItem = newItem。

因为对属性赋值内部会有一个release的过程,所以这里不用事先判断并置为nil。

例4:NSArray或NSMutableArray相关

- (id) init
{
self = [super init];
if (self)
{
videoList = [[NSMutableArray array]retain];
}
return self;
}

- (void) loadData
{
void(^success)(NSMutableArray *array) = ^(NSMutableArray *array){
videoList = array;
NSLog(@"videoListcount:%d", [videoList count]);
[self.collectionView reloadData];
};
……
}


这里有两个内存错误。第一是videoList首先retain了一块内存,然后videoList再指向array所在的内存,先前那块内存就泄露了。第二是videoList = array;array可能被自动释放,可改为

[videoList addObjectsFromArray:array];

或先把videoList = [[NSMutableArray array] retain];这句删掉,然后

videoList = [array retain];

10 检查内存管理问题的方式

1)点击Xcode顶部菜单中的ProductàAnalyze。这种方法主要可以查看内存泄露,变量未初始化,变量定义后没有被使用到

2)使用Instrument工具检查。点击Xcode顶部菜单中的ProductàProfile,弹出一个界面,选择左侧的Memory后,再选右侧的Leaks。

3)人工检查

11 代理使用不当也会造成崩溃

1)执行代理方法前要判断

if(_delegate&& [_delegate respondsToSelector:@selector(doSomething)])
{
[_delegate doSomething];
}


2)对象释放前若有代理,则把代理置为nil

- (void)dealloc
{
_someObject.delegate = nil;
release_nil(_someObject);
[super dealloc];
}


3)所有声明的协议都要遵守<NSObject>协议

@protocol MyDelegate <NSObject>
- (void)myMethod;
@end
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: