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

内存管理与属性的参数

2016-02-25 15:57 211 查看

内存管理的基本范围和概念.

程序运行过程中药创建大量的对象, 和其他高级语言类似,在ObjC中对象存储在堆区,程序不会自动释放内存. 如果对象创建使用后没有及时释放,那么就会占用大量内存. 其他高级语言都是通过垃圾回收机制来解决.在ObjC中并没有,所以需要手动维护.

目的:移动设备的内存非常有限,应对iOS系统对app内存限制.

iOS在应用程序使用超过20M,40M分别发送MemoryWaring消息警告一次并在120M时强制退出.

管理范围:只有对象.

因为对像存储在内存的堆区,而堆区内存是需要程序员手动管理,

而对象指针变量存在于栈区,会在作用域结束时自动释放,指针栈区和对象堆区的释放不同步也就造成野指针和内存泄露.

核心原则:引用计数器

对象所有权:任何对象都可能由一个或多个拥有者,只有所有权为0可以释放.

过早释放会造成野指针,僵尸对象. 不释放则造成没有指针指向,程序员不能操作,且占内存,内存泄漏

Cocoa所有权策略:任何自己创建的对象都归自己所有

Person *p=[[Person alloc]init]; //创建时会是引用计数器为1.所有权归p所有.


对象使用一个计数器来保存当前对象被拥有者引用数.计数器8个字节NSUInterage;计数器为0,会触发系统自动调用的临终遗言dealloc.

内存管理的分类

MRC(手动管理,Xcode4,1之前).

ARC自动管理.现在默认使用.

“垃圾回收机制” (ObjC不支持)

ARC是编译器特性,其他是运行时特性.

1.MRC手动内存管理

1.手动输入retain/release方法来增加/减少引用计数器.达到灵活内存释放.

2.retainCount方法可以返回计数器数值,不过不是一定准确,不建议使用.例:计数器为0时也显示1.

3.原则:

1)只要对象还被使用,不回收.

2)只要你想使用,+1(发送retain消息)(防止他人使用完毕后release释放了.)

3)不使用了,-1(发送release消息). **

总结:谁创建,谁release;谁retain谁release.有始有终,有增有减.

ObjC中的内存管理机制跟C语言中指针的内容是同样重要的,要开发一个程序并不难,但是优秀的程序则更测重于内存管理,它们往往占用内存更少,运行更加流畅。虽然在新版Xcode引入了ARC,但是很多时候它并不能完全解决你的问题。在Xcode中关闭ARC:项目属性—Build Settings–搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。

2.野指针

僵尸对象:已经释放的对象(不能再使用).

野指针:指向僵尸对象(不可用内存)的指针;

空指针,没有指向的指针(nil);

内存泄露:指向对象的指针被取消;

使用僵尸对象,使用野指针,依然能获取值,这个值可能是之前的,也可能是重新使用内存存储的值;格式不确定,就是垃圾值;(可以打开僵尸对象检测来报错)

_nil NIL NULL区别

nil:对象初始赋值,对象值

NIL: 类对象值

NULL:通用指针 a null point to an Object else_

3.单个对象(野指针)僵尸对象和内存泄漏问题.

野指针:最后一次release后,并栓住野指针 => p=nil;(如果不设置,则p就是野指针,它的指向已经不属于这个程序,因此是很危险的.并且设置之后,再给p发送release消息不会报错.因为此时是nil)

典型报错:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT. 访问了一块已经不属于你的内存.

内存泄漏:计数器没有到0,而指向对象的指针指向了别处或是作用域结束被释放.此时这个对象无法找到,无法操作.即造成内存泄漏. (无法解决,只能程序结束释放.所以尽力避免);

4.多个对象的内存管理.

总结而言就两个错误:1.释放了还要用—不用了还不释放

多个对象主要是指针对关联关系的对象,此时一个对象拥有对象类型的实例变*量.调用时相当于多了一个所有权(例set方法设置_car=car,对象型指针*_car指向了car,所以对象car需要retain一次).

为了解决这个问题可以重写Person类的set方法

-(void)setCar:(Car *)car{
_car=[car retain];
}//一般内存管理的方法返回值都是对象自己.


虽然方法中符合了原则,但是在主函数部分,如果调用一次set方法,就会使car对象计数器+1,而在主函数中不能release,不然主函数部分就违反了原则(谁创建,谁release;谁retain谁release.)而此时car对象引用计数器始终多一个,内存泄露

怎么办?–利用Person类释放时系统自动调用的dealloc方法在里面加上car.(创建对象p调用car);

//书写规范

//1,先释放子类自己的(关系)对象

//2,必须最后要写[super dealloc(父类有一部分占据了子类对象空间)

-(void)dealloc:{
[_car release];
[super dealloc];
}


_car此时代表Person类的关联对象Car类的对象)目的:跟set的retain对称了;

同理,根据”想用+1.不用-1”原则:当Person有多个关系对象时 ,用多个set赋值,但是dealloc只能释放最后一次设置的对象和本身;所以==>在set中还需要释放上一次的设置的对象.还要加上
[_car release]
////如果第一次用set方法,_car一开始没指向时,_car等于nil,而[nil release]没有作用,所以可以放心使用;可是问题又来了.如果连续set同一个对象该如何.此时计数器由1->0->1;僵尸对象错误;所以需要价格判断;

因此最后set完整写法如下:

-(Void)setCar(car *)car{
if(_car != car){       // 防止两个相同对象造成僵尸对象
[_car release];      // 防止两个不同对象造成内存泄露
_car = [car retain];   //  1个对象—僵尸对象
}
}


ARC自动内存管理

1.移动设备内存及其有限,每个app所能占用的内存是有限制的

2.下列行为会增加内存:

创建一个oc对象

定义一个变量

调用一个函数或者方法


ARC是编译器特性(如@property,点语法),启用ARC之后,编译器就会自动在适当地方retain, release,autorelease(会在对象创建时自动添加) 语句.不是垃圾回收机制的运行时特性.

指针保持对象生命

ARC的原则:只要还有一个指针指向对象,对象就会保持在内存中.没有则会释放.与MRC原则区别:强指针指向.(避免了一般情况的僵尸对象和内存泄露)使对象寿命和指针变量一致了,不考虑引用计数器..

弱指针 _ _weak 修饰的

__weak Person * p; 弱指针指向的对象释放后,自动指向nil..

weak指针

_ _weak NSString *str =[[NSString alloc]initWithFormat:...];

NSLog(@"%@", str); // will output "(null)"


上例中String的对象没有拥有者,在创建之后就会被立即释放,会警告.其实一般不加前缀,编译器默认strong,不加也行.

weak 指针主要用于“父-子”关系,父亲拥有一个儿子的 strong 指针, 因此是儿子的所有者;但为了阻止所有权回环,儿子需要使用 weak 指针指向父亲.

有了ARC之后,可以不需要考虑retain或release对象,只需要考虑对象间的关系.虽然 ARC 管理了 retain 和 release,但并不表示你完全不需要处理 内存管理的问题。因为 strong 指针会保持对象的生命,某些情况下你 仍然需要手动设置这些指针为 nil(指正变量作用域结束释放,如果作用域过大),否则可能导致应用内存不足,无论何时你创建一个新对象时,都需要考虑谁拥有该对象,以及这个对象需

要存活多久。

Xcode 的 ARC 自动迁移 要启用一个项目的 ARC,你有以下几种选择:

Xcode 带了一个自动转换工具,可以迁移源代码至 ARC

你可以手动转换源文件

你可以在 Xcode 中禁用某些文件使用 ARC,这点对于第三方库非常有用。

dealloc 方法

另外启用 ARC 之后,dealloc 方法在大部分时候都不再需要了,因为你不能 调用实例对象的 release 方法,也不能调用[super dealloc]。假如原先的 dealloc 方法只是释放这些对象,Xcode 就会把 dealloc 方法完全移除。你不再 需要手动释放任何实例变量。

如果你的 dealloc 方法处理了其它资源(非内存)的释放,如定时器、Core Foundation 对象,则你仍然需要在 dealloc 方法中进行手动释放,如 CFRelease(), free()等。这时 Xcode 会保留 dealloc 方法,但是移除所有的 release 和[super dealloc]调用。如下:

-(void)dealloc{
AudioServiceDisposeSystemSoundID(soundID);
}


ARC下的对象管理

强弱指针都可以访问对象

ARC下set方法参数retain无用了.增加weak和strong参数.

关联关系中.@property(nonatomic,weak)Dog*dog; 用弱指针预防内外不同步(外部强指针转向时,对象立即释放不收方法调用影响)

如果用strong,不同步,不过作用域结束后,mian中指针变量都会释放,之后调用对象会被释放,而被调对象因为调用对象的释放,其中的指针变量也释放了,最后调用对象也会被释放.

引申的:当ARC中循环引用出现时,(你拥有我我拥有你,只是一首太温柔的歌~)

1)此时防止编译错误还是先使用@Class,.m中再#import类头文件.

2)之后类似于MRC下改变一个@property 的set方法参数retain,把其中一个强指针替换成weak(其实不论是否循环推荐以后都使用weak)但是有一个要注意:
__weak Person *p=[Person new];
这种对象创建昙花一现,没有任何意义.

ARC和MRC混编

MRC>ARC

把MRC的代码转换成ARC的代码,删除内存管理操作(手动)

xcode提供了自动将MRC转换成ARC的功能,操作菜单栏edit -> Refacotor(重构) -> Convert to Objective-C ARC

ARC>MRC

在ARC项目中继续使用MRC编译的类,在编译选项中标识MRC文件即可”-fno-objc-arc”

在MRC项目中继续使用ARC编译的类在编译选项中标识MRC文件即可”-fobjc-arc”

5.属性参数

像上面的set方法编写情况很多, 可以使用@property的参数来帮我们自动实现.

参数类别



retain:操作对象类型//release旧值,retain新对象,判断新旧不是 一个对象;//用了就一定要记得重写dealloc方法

另外还可以set,get方法换名:@property(setter=setisage:)intage;//记得set方法名后有:

atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。

atomic

设置成员变量的@property属性时,默认为atomic,提供多线程安全。

在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,

setter函数会变成下面这样:

@synchronized(self){
if(_car=!car){
[_car release];
_car=[car retain];
}


nonatomic

禁止多线程,变量保护,提高性能。

atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。

这也就是说,在多线程环境下,解析的访问器默认提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。如果你不指定 nonatomic ,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。

assign

对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char)等等。

此标记说明设置器直接进行赋值,这也是默认值。在使用垃圾收集的应用程序中,如果你要一个属性使用assign,且这个类符合NSCopying协议,你就要明确指出这个标记,而不是简单地使用默认值,否则的话,你将得到一个编译警告。这再次向编译器说明你确实需要赋值,即使它是 可拷贝的。

retain

对其他NSObject和其子类对参数进行release旧值,再retain新值

指定retain会在赋值时唤醒传入值的retain消息。此属性只能用于Objective-C对象类型,而不能用于Core Foundation对象。(原因很明显,retain会增加对象的引用计数,而基本数据类型或者Core Foundation对象都没有引用计数——译者注)。

注意: 把对象添加到数组中时,引用计数将增加对象的引用次数+1。

copy

对NSString 它指出,在赋值时使用传入值的一份拷贝。拷贝工作由copy方法执行,此属性只对那些实行了NSCopying协议的对象类型有效。更深入的讨论,请参考“复制”部分。

assign与retain:

接触过C,那么假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时a和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块内存的时候会引起程序crash掉。

了解到1中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候,代表该内存不再被任何指针所引用,系统可以把它直接释放掉。

总结:上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。

循环retain问题;

循环retain的场景 互相拥有;

比如A对象retain了B对象,B对象retain了A对象 循环retain的弊端 这样会导致A对象和B对象永远无法释放

循环retain的解决方案 :

当两端互相引用时,应该一端用retain、一端用assign即可

@class的作用

在.h文件中声明,告诉编译器@Class 修饰的标示符是一个类,编译不会报错,这是为了防止如果有过多的头文件都#import同一个文件,或是这些依次#import,那么一旦最开始的头文件稍作改动,之后用到这个文件的所有类都要重新编译一边,效率非常低.但是,@Class并不会包含类的内容,所以具体实现还是需要在.m文件中调用类的头文件.

在MRC中,如果出现循环引用中,不仅头文件循环引入会编译报错,而且会出现循环retain问题(set方法)造成对象永远无法释放.

**第一个问题用@Class解决,第二个是需要声明两个对象类型实例变量时@property参数一个正常用retain,一个要换成assign.并且dealloc重写时不用release;

在ARC中只需@Class解决即可.

7.自动释放池

ObjC中的一种内存自动释放机制,与其他高级语言不同, 这是一种半自动的机制,有些操作还需要我们手动设置. 自动内存释放使用@autoreleasepool关键字声明一个代码块, 如果一个对象在初始化时调用了autorelease方法,那么当代码块执行完毕后,在块中调用过autorelease方法的对象都会自动调用一次release方法, 这样起到了自动释放的作用,同时对象的销毁也得到了延迟

在程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在,(先进后出);

当一个对象(在池作用域内)调用autoreslease时,系统会登记之,并在池作用域结束后向对像发送且仅一条release消息.

由于OC内存管理原则:谁创建,谁释放.但是如果一个方法返回了一个新建的对象,该对象如何释放.而方法中不能写release,这样一创建就会释放,所以引入了释放池概念—在作用域内延迟释放对象.

一般管理内存方法的返回值都是对象本身:release,retain,autorelease

@autoreleasepool{
Person *p=[Person new];
[P autorelease];//把对象加入此释放池
}//释放


注意:

只有在池作用域中调用autorelease方法才会加入相应释放池,有多个嵌套时就近上一层的.大括号结束即结束. 引用释放池后OC内存管理规则可理解:一个申请配对一个autorelease.一个retain配对一个release;

应用场景: 1)在方法中创建了对象(注意在池域中调用);

-(void)run{
[[[Person alloc]init]autorelease];
}


2)快速创建对像

+(instancetype)per{
return [[Person new]autorelease];
}//Person换成self普适性更高;id换成instancetype稳定性更高
在main中调用就可快速创建对应对象,且不用release;


3)快速创建对象并初始化

-(instancetype)initWithAge:(int)age{
if(self=[super init]){
_age=age;
}
+(instancetype)personWithAge:(int)age{
return [[[self alloc]initWithAge:age]autorelease];
}


OC的类库中静态方法一般已经同上调用了autorelease方法,所以不需要手动释放;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios 内存管理