IOs内存管理(二)
来源:互联网 发布:java构造方法语法 编辑:程序博客网 时间:2024/05/17 03:43
一 . MRC 序
1. 举例在MRC中实现set get 方法(assign状态下,retain状态下,copy状态下)
比如有一个引擎类Engine,有一个汽车类Car,Car里面有一个Engine的实例变量,一个setter和getter方法。具体如下
[plain] view plaincopy
- #import "Car.h"
- @implementation Car
- -(void)setEngine:(Engine*) engine
- {
- _engine=engine;
- }
- -(Engine*)engine
- {
- return _engine;
- }
- -(void)dealloc
- {
- NSLog(@"Car is dealloc");
- [super dealloc];
- }
- @end
上面写的是一个简单的类,当让这样写是有问题,所以需要一步步的改进。
第一步改进:
先使用它看问题的所在,在main方法里面如下使用:
[plain] view plaincopy
- //先创建一个引擎
- Engine* engine1=[[Engine alloc]init];
- [engine1 setID:1];
- //在创建一个汽车,设置汽车的引擎
- Car* car=[[Car alloc]init];//retainCount=1
- [car setEngine:engine1];
- /*分析:在这里,现在有两个引用指向这个Engine对象,engine1和Car中的_engine,可是这个Engine对象的引用计数还为1,因为在
- set方法中,并没有使用retain。那么不管是哪个引用调用release,那么另外一个引用都会指向一块释放掉的内存,那么肯定
- 会发生错误。所以需要在set方法中加以改进。*/
- 第二步改进:
setter方法改进
[plain] view plaincopy
- -(void)setEngine:(Engine*) engine
- {
- _engine=[engine retain];//多了一个引用,retainCount+1
- }
再在main中使用它
[plain] view plaincopy
- //先创建一个引擎
- Engine* engine1=[[Engine alloc]init];
- [engine1 setID:1];
- //在创建一个汽车,设置汽车的引擎
- Car* car=[[Car alloc]init];//retainCount=1
- [car setEngine:engine1];//retainCount=2,因为使用了retain,所以retainCount=2,
- //假设还有一个引擎
- Engine* engine2=[[Engine alloc]init];
- [engine2 setID:2];
- //这个汽车要换一个引擎,自然又要调用settr方法
- [car setEngine:engine2];
- /*分析:在这里,汽车换了一个引擎,那么它的_engine就不在指向engine1的哪个对象的内存了,而是换成了engine2,也就是说engine1的哪个对象指向的内存的引用只有一个
- 可是它的retainCount是两个,这就是问题的所在了。所以仍然需要改进*/
第三步改进:
[plain] view plaincopy
- -(void)setEngine:(Engine*) engine
- {
- [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
- _engine=[engine retain];//多了一个引用,retainCount+1
- }
再在main'中使用
[plain] view plaincopy
- //先创建一个引擎
- Engine* engine1=[[Engine alloc]init];
- [engine1 setID:1];
- //在创建一个汽车,设置汽车的引擎
- Car* car=[[Car alloc]init];//retainCount=1
- [car setEngine:engine1];//retainCount=2,因为使用了retain,所以retainCount=2,
- //如果进行了一个误操作,又设置了一次engine1
- [car setEngine:engine1];
- /*分析:那么,又要重新调用一次setter方法,这根本就是无意义的操作,浪费资源,所以要在设置之间加上判断*/
第四步改进:
[plain] view plaincopy
- -(void)setEngine:(Engine*) engine
- {
- if(_engine!=engine){//判断是否重复设置
- [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
- _engine=[engine retain];//多了一个引用,retainCount+1
- }
- }
第五步:
现在setter方法基本没有问题了,那么在当我们要释放掉一个car对象的时候,必须也要释放它里面的_engine的引用,所以,要重写car的dealloc方法。
[plain] view plaincopy
- -(void)dealloc
- {
- [_engine release]; //在释放car的时候,释放掉它对engine的引用
- [super dealloc];
- }
这还不是最好的释放的方法,下面的方法更好
[plain] view plaincopy
- -(void)dealloc
- {
- [_engine setEngine:nil]; //在释放car的时候,对setEngine设置为nil,它不仅会release掉,并且指向nil,即使误操作调用也不会出错。
- [super dealloc];
- }
所以,综上所述,在setter方法中的最终写法是
[plain] view plaincopy
- <span style="color:#CC66CC;">-(void)setEngine:(Engine*) engine
- {
- if(_engine!=engine){//判断是否重复设置
- [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
- _engine=[engine retain];//多了一个引用,retainCount+1
- }
- }</span>
然后在dealloc方法中写法是:
[plain] view plaincopy
- <span style="color:#CC66CC;">-(void)dealloc
- {
- [_engine setEngine:nil]; //在释放car的时候,对setEngine设置为nil,它不仅会release掉,并且指向nil,即使误操作调用也不会出错。
- [super dealloc];
- }</span>
六、property中的setter语法关键字
在property属性中有三个关键字定义关于展开setter方法中的语法,assgin(缺省),retain,copy。当然这三个关键字是互斥的。
1、assgin展开stter的写法
[plain] view plaincopy
- -(void)setEngine:(Engine*) engine
- {
- _engine=engine;
- }
2、retain展开的写法
[plain] view plaincopy
- -(void)setEngine:(Engine*) engine
- {
- if(_engine!=engine){//判断是否重复设置
- [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
- _engine=[engine retain];//多了一个引用,retainCount+1
- }
- }
3、copy展开的写法
[plain] view plaincopy
- -(void)setEngine:(Engine*) engine
- {
- if(_engine!=engine){//判断是否重复设置
- [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
- _engine=[engine copy];//多了一个引用,retainCount+1
- }
- }
对于copy属性有一点要主要,被定义有copy属性的对象必须要符合NSCopying协议,并且你还必须实现了-(id)copyWithZone:(NSZone*)zone该方法。
可以看到,使用retain和我们上面举得例子完全相同,所以我们可以使用property和它的retain代替之前的写法。
#import <Foundation/Foundation.h>
#import "Engine.h"
@interface Car : NSObject{
Engine * _engine;
}
@property (nonatomic,retain)Engine * engine;
@end
#import "Car.h"
@implementation Car
@synthesize engine = _engine;
//这个是retain展开的setter方法
- (void)setEngine:(Engine *)engine{
if(_engine != engine){
[_enginerelease]; //这里不用了要加上release
_engine = [engineretain];
NSLog(@"_engine----%@",_engine);
}
//这里传递进来的engine的地址就不同了
//赋值方法执行了两次_engine----<Engine: 0x100203ac0>
//第二次 _engine----<Engine: 0x1001023e0
}
/*
添加完release后的
2015-12-15 10:53:17.573 MRCDeom[6560:380091] _engine----<Engine: 0x100400150>
2015-12-15 10:53:17.575 MRCDeom[6560:380091] Car--retainCount--1---<Car: 0x100400160>
2015-12-15 10:53:17.576 MRCDeom[6560:380091] engine1----retainCount-2----<Engine: 0x100400150>
2015-12-15 10:53:19.776 MRCDeom[6560:380091] _engine----<Engine: 0x1002006b0>
2015-12-15 10:53:19.777 MRCDeom[6560:380091] Car--retainCount--1---<Car: 0x100400160>
2015-12-15 10:53:19.777 MRCDeom[6560:380091] engine1----retainCount-1----<Engine: 0x100400150>
2015-12-15 10:53:22.592 MRCDeom[6560:380091] engine2---retainCount-2----<Engine: 0x1002006b0>
*/
- (Engine * )engine{
return_engine;
}
- (void)dealloc{
[_enginerelease];
[superdealloc];
}
@end
#import <Foundation/Foundation.h>
#import "testDemo.h"
#import "Car.h"
#import "Engine.h"
int main(int argc,const char * argv[]) {
@autoreleasepool {
Engine * engine1 = [[Enginealloc]init];
Car * car1 = [[Caralloc]init];
[car1 setEngine:engine1];
NSLog(@"Car--retainCount--%ld---%@",car1.retainCount,car1);//Car--retainCount--1---<Car: 0x100605b50>
NSLog(@"engine1----retainCount-%ld----%@",engine1.retainCount,engine1);//engine1----retainCount-2----<Engine: 0x100603a20>
Engine * engin2 = [[Enginealloc]init];
[car1 setEngine:engin2];
NSLog(@"Car--retainCount--%ld---%@",car1.retainCount,car1);//Car--retainCount--1---<Car: 0x100605b50>
NSLog(@"engine1----retainCount-%ld----%@",engine1.retainCount,engine1);//engine1----retainCount-2----<Engine: 0x100603a20>
NSLog(@"engine2---retainCount-%ld----%@",engin2.retainCount,engin2);//engine2---retainCount-2----<Engine: 0x100200090>
}
//当超出了作用域后,test超出了作用域,强引用失效了。自动的释放了testDemo对象。
//testDemo对象的所有者不存在了,因此废弃该对象。这个对象的obj_成员变量也就被废弃了。
return0;
}
(2)@property 参数
2.1) 控制set方法的内存管理
retain:release掉旧值,retain 新值(OC对象)
assign:直接赋值,不做任何内存管理(默认的,用于非OC对象)
copy:release旧值,copy新值(一般用于NSString *)
2.2) 控制需不需要生成set方法
readwrite:同时生成set方法和get方法(默认)
readonly: 只会生成get方法
2.3)多线程管理
atomic 和 nonatomic用来决定编译生成的getter和setter是否为原子操作。
atomic :性能低。线程安全的,多线程环境下,原子操作是必须要的。
nonatomic:性能高(iOS开发中使用)禁止多线程,变量保护,提高性能
2.4)区分assign,retain,copy
assign
对基础数据类型(NSInteger,CGFloat)和 C数据类型 (int,float,double,char);简单的赋值,不更改索引计数。
retain
释放掉旧的对象,将旧对象的值赋予输入对象,在提高输入对象的索引计数为1.
copy
建立一个相同的对象,新建立的对象的索引计数为1.然后释放掉就对象;
retain是指针拷贝,copy是内容拷贝,在拷贝前都会释放旧对象;
从网上找到了一些资料,觉得讲的有道理
copy与retain:
Copy其实是建立了一个相同的对象,而retain不是:
1.比如一个NSString对象,地址为0×1111 ,内容为@”STR”,Copy到另外一个NSString 之后,地址为0×2222,内容相同。
2.新的对象retain为1 ,旧有对象没有变化retain到另外一个NSString 之后,地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1。
总结:retain 是指针拷贝,copy是内容拷贝。
assign与retain:
1. 接触过C,那么假设你用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的时候,代表该内存不再被任何指针所引用,系统可以把它直接释放掉。
总结:上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。
assign没有引用计数的概念,retain有引用计数的概念;
二 . ARC 自动引用计数的内存管理
1.ARC的注意点和优点
(1)clang(LLVM)3.0或以上版本;(2)不允许调用对象的release方法;(3)不在重写dealloc方法;(4)ARC是编译器的特性,不是运行时特性;
2.ARC状态下 追加了所有权声明
OC编程中为了处理对象,可将变量类型定义为id,各种对象类型;
(1)对象类型:指向NSObject这样的OC类的指针;
(2) id类型是用于隐藏对象类型的类名,相当于C中的void *
在ARC有效的时候,id类型和对象类型,与C语言类型不同,这些类型必须附加上所有权修饰符
__strong; __weak; __unsafe_unretained; __autoreleasing;
(1.a)__strong 是id类型和对象类型默认的所有权修饰符
ARC有效:
id obj = [[NSObject alloc] init];
ARC无效:
{
id __strong obj = [[NSObject alloc] init];
[obj release];
}
以上两种状态是等效的:__strong 修饰符的变量obj在超出变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。 __strong修饰符表示对对象的“强引用”持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
ARC状态下的思路:
(1)自己生成的对象,自己持有;
(2)非自己生成的对象,自己也能持有;使用了默认的强引用,可以把非自己生成的对象变成是自己的持有的。
对象所有者和对象的生存周期也是固定的(在变量的作用内,在变量赋值中都是有效的)
eg:
id objA = [[NSObject alloc] init]; A
id objB = [[NSObject alloc] init]; B
id objC = nil;
//此时 objA持有A对象; objB持有B对象;objC不持有对象;
objA = objB
//此时 objB把B赋值给了objA 此时objA持有B对象,则对A对象的强引用就失效了。A对象被废弃了;
objC = objA;
//此时objC获取了B的强引用;
注意:__strong, __weak, __autoreleasing 着几个修饰符可以保证将附有这些修饰符的自动变量初始化为nil;
总述:“自己生成的对象,自己持有”和“非自己生成的对象,自己也能持有”只需通过对带strong修饰符的变量赋值便可以达到。通过废弃带strong修饰符的变量或者对变量赋值,都可以做到“不再需要自己持有的对象时释放”。 “非自己持有的对象无法释放”由于不必再键入release,所以原本就不会执行。
(1.b)weak 修饰符
strong看似很完美,但是却无法解决引用计数式内存管理中必然会发生的“循环引用”的问题。
id test0 = [[Test alloc] init]; A
//test0持有 Test对象A的强引用;
id test1 = [[Test alloc] init]; B
//test1持有 Test对象B的强引用;
[test0 setObject:test1];
//test对象A的obj成员变量只有Test对象B的强引用;持有B的强人用的变量:Test 对象A的obj和test1;
[test1 setObject:test0];
//test对象B的obj成员变量只有Test对象A的强引用;持有A的强人用的变量:Test 对象B的obj和test0;
test0超出作用域后,强引用失效,所以自动释放Test对象A;
test1超出作用域后 ,强引用失效,所以自动释放Test对象B;
此时,持有Test对象A的强引用的变量为 Test 对象B的obj;
持有Test对象B的强引用的变量为 Test 对象A的obj;
就发生了内存的泄露。循环引用容易发生内存泄露,所谓内存泄露就是应当废弃的对象在超出其生存周期后继续存在。
解决的方案:weak修饰符,弱引用不能持有对象实例。
id __weak obj = [[NSObject allic]init];
这是为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会被立即释放掉。
id __strong obj = [[NSObject allic]init];
id __weak obj1 = obj;
obj0 变量超出作用域,强引用失效。
weak修饰符还有一个优点:在持有某对象的弱引用时,若该对象被废弃了,则次弱引用将自动失效,且处于nil(空引用)
(1.c) __unsafe_unretained修饰符
因为weak修饰符只能在ios5以上版本使用,iOS一下的可以使用__unsafe_unretained
__unsafe_unretained是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但是__unsafe_unretained修饰的变量不属于编译器的内存管理对象。
__unsafe_unretained和weak一样,自己生成并持有的对象,不能继续为自己所有,所以生成的对象会立即释放掉。在使用__unsafe_unretained修饰符时,赋值给附有strong修饰符的变量时与必要确保被赋值的对象确实存在。
(1.d) __autoreleasing修饰符
在ARC有效时,autorelease无法直接使用。但是autorelease功能是起作用的。
@autoreleasepool块 来替代“NSAutoreleasePool类对象生成、持有以及废弃”
ARC有效时,要通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。
(1)非显示的使用__autoreleasing修饰符也可以
a. 取得非自己生成并持有的对象时,既可以使用alloc,new,copy,mutableCopy以外的方法取得对象。但该对象已经被注册到autoreleasepool。这是因为编译器会检查方法名是否已alloc,new,copy,mutableCopy开始,如果不是就自动将返回值的对象注册到autoreleasepool。
+ (id) array{
id obj = [[NSMutableArray alloc]init];
return obj;
}
这里也没有显示的指定所有权修饰符,但是return 使得变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,作为函数的返回值,编译器会自动将其注册到autoreleasepool
(2)为什么在访问附有__weak 修饰符的变量时,必须访问注册到autoreleasepool的对象?
a. weak 修饰符只支持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃,如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。
继续学习吧。少年!
- iOS:内存管理(二)
- IOs内存管理(二)
- iOS内存管理(二)
- iOS 之OC内存管理(二)
- IOS开发中的内存管理(二)
- iOS入门(二十三)内存管理
- 内存管理(二)
- 内存管理(二)
- 内存管理(二)
- 内存管理(二)
- 黑马程序员------ios培训 oc内存管理(二)
- iOS开发之OC内存管理(二)
- iOS/OS X内存管理(二):借助工具解决内存问题
- C#内存管理(二)
- AMPS:内存管理(二)
- WinCE内存管理(二)
- 操作系统 内存管理(二)
- STL内存管理(二)
- 移植DS1302到CC3200
- Swift-贝赛尔曲线画扇形、弧线、圆形、多边形——UIBezierPath实现App下载时的动画效果
- linux 基本常用命令整理
- HTML5的思考
- 在Qt中使用C++代码创建界面
- IOs内存管理(二)
- 解决POC脚本对多种URL的自适应问题
- python网络入门:urllib.request模块和urllib.urllib.parse模块
- UVa 1343 The Rotation Game(IDA*)
- Android开发环境成功搭建
- 设计模式(7)--策略模式(2)
- Android之Volley框架源码分析
- oracle创建表空间
- chrome浏览器广告屏蔽插件adblock下载地址