引用计数count
2015-11-28 20:52
162 查看
Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
1. (Garbage Collection)自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带设备里面,资源是紧俏商品所以iPhone不支持这个功能。所以“Garbage
Collection”不是本入门指南的范围,对“Garbage Collection”内部机制感兴趣的同学可以参考一些其他的资料,不过说老实话“Garbage Collection”不大适合适初学者研究。
解决: 通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了.
2. (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变
成员了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。
解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期.
3. (NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机.
解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.
#import
<Foundation/Foundation.h>
@protocol
____count <NSObject>
基础
1.
<1>assign (MRC)
//setter方法
- (void)setName:(NSString *)name
{ _name = name;}
//getter方法
- (NSString *)name
{return
_name;}
<2>retain (MRC)
//setter方法
- (void)setName:(NSString *)name
{if
(_name != name){
[_name release];
_name = [name retain];}}
//getter方法
- (NSString *)name
{return
_name;}
<3>copy (MRC)
//setter方法
- (void)setName:(NSString *)name
{if
(_name != name){
[_name release];
_name = [name copy];}}
//getter方法
- (NSString *)name
{return
_name;}
<4>初始化
self.firstName中调用了firstName的setter方法(setter方法中retain过一次,所以可以用此方法,效率没有直接retain的方法高)
- (KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
if (self
= [super
init]) {
self.firstName = firstName;
self.lastName = lastName;
self.phoneNumber = phoneNumber;
}
return
self;
}
或者
- (KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
if (self
= [super
init]) {
_firstName = firstName;
_lastName = lastName;
_phoneNumber = phoneNumber;
}
return
self;
}
2.
<1>伪拷贝
- (id)copyWithZone:(NSZone *)zone
{return
[self
retain];}
<2>浅拷贝
- (id)copyWithZone:(NSZone *)zone
{NSString *name =
self.name;
NSUInteger age =
self.age;
Person *Person = [[Person alloc] initWithName:name age:age];
return
Person;}
<3>深拷贝
- (id)copyWithZone:(NSZone *)zone
{ NSString *name =
self.name;
NSUInteger age =
self.age;
NSString *newName = [NSString stringWithFormat:@"%@",name];
Person *newPerson = [[Person alloc] initWithName:newName age:age];
return
newPerson;}
举例
1.
NSArray, NSDictionary, NSSet等类,会在对象加入后引用计数加一获得所有权,在对象被移除或者整个容器对象被释放的时候释放容器内对象的所有权。类似的情况还有UIView对subview的所有权关系,UINavigationController对其栈上的controller的所有权关系等等。
//push入栈显示新的视图控制器,被导航控制器管理,retain+1,如果back后被推回去,自动deallco,pop后引用计数-1
//push后detailVC进入navigationController的viewControllers数组栈中,相当于UIView中的subviews,数组销毁,里面的引用计数全部-1
[self.navigationController pushViewController:detailVC animated:YES];
//???
还有一些用法会让系统拥有对象的所有权。比如NSObject
的performSelector:withObject:afterDelay。如果有必要,需要显示的调用cancelPreviousPerformRequestsWithTarget:selector:object:,否则有可能产生内存泄露。
因这种原因产生的泄露因为并不违反任何规则,是Intrument所无法发现的。
3.@property(nonatomic,
copy)void(^PassVauleBlock)(UIImage*)
passVaule;
必须这么写
block是个代码片段要用copy
@property(nonatomic,
copy) NSMutableArray *contacts;
这么写是错的,不可变数组没有copy方法,实现的是父类不可变数组中copy的方法,copy出的是不可变数组
4.@property(nonatomic,
assign)id<UIScrollViewDelegate>delegate;
为什么delegate
用
assign
而不使用
retain (应为不用管代理的死活,代理死了不必完成协议)
一个对象没必要管理自己delegate的生命周期,或者说没必要拥有该对象,所以我们只要知道它的指针就可以了,用指针找到对象去调用方法,也就是委托实现的感觉。
或者我们换个角度,从内存管理方面也可以解释这个问题。delegate的生命周期不需要让该对象去控制,如果该对象对其使用retain很可能导致delegate所指向的对象无法正确的释放。
循环引用
所有的引用计数系统,都存在循环应用的问题。例如下面的引用关系:
对象a创建并引用到了对象b.
对象b创建并引用到了对象c.
对象c创建并引用到了对象b.
这时候b和c的引用计数分别是2和1。当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中。
这种情况,必须打断循环引用,通过其他规则来维护引用关系。比如,我们常见的delegate往往是assign方式的属性而不是retain方式的属性,赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。如果一个UITableViewController对象a通过retain获取了UITableView对象b的所有权,这个UITableView对象b的delegate又是a,如果这个delegate是retain方式的,那基本上就没有机会释放这两个对象了。自己在设计使用delegate模式时,也要注意这点。
因为循环引用而产生的内存泄露也是Instrument无法发现的,所以要特别小心。
5.@property
(nonatomic,
retain,
readonly) UILabel *contentLabel;
@property
(nonatomic,
retain,
readonly) UIButton *submitButton;
readonly没有setter方法,只有getter方法
在.m文件中//没有setter方法用不了_contentLabel,需自己创建实例变量(@synthesize
实现setter和getter的方法)
@synthesize
contentLabel = _contentLabel;
@synthesize
submitButton = _submitButton;
- (UILabel *)contentLabel{
if (!_contentLabel) {
//没有setter方法,没有内部retain,
不能加autorelease,
保存对象的地址,
在dealloc中release,这里引用计数需+1
_contentLabel = [[UILabel alloc] initWithFrame:self.view.bounds];
//如果创建对象时先getter button,lable就会遮挡button
//[self.view addSubview:_contentLabel];
//保证lable永远在最下面
[self.view insertSubview:_contentLabel atIndex:0];
}
return
_contentLabel;
}
(UIButton *)submitButton{
if (!_submitButton) {
//引用计数+1
_submitButton = [[UIButton buttonWithType:UIButtonTypeSystem] retain];
[self.view addSubview:_submitButton];
}
return
_submitButton;
}
-(void)dealloc{
[_contentLabel release];
[_submitButton release];
[super
dealloc];
}
6.//保存从数组中移除的数据要retain一次,要不然数组元素出数组内存引用计数会自动减到0,数据销毁
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath {
id object = [_datasource[fromIndexPath.row] retain];
[_datasource removeObjectAtIndex:fromIndexPath.row];
[_datasource insertObject:object atIndex:toIndexPath.row];
[object release];
}
@end
1. (Garbage Collection)自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带设备里面,资源是紧俏商品所以iPhone不支持这个功能。所以“Garbage
Collection”不是本入门指南的范围,对“Garbage Collection”内部机制感兴趣的同学可以参考一些其他的资料,不过说老实话“Garbage Collection”不大适合适初学者研究。
解决: 通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了.
2. (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变
成员了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。
解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期.
3. (NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机.
解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.
#import
<Foundation/Foundation.h>
@protocol
____count <NSObject>
基础
1.
<1>assign (MRC)
//setter方法
- (void)setName:(NSString *)name
{ _name = name;}
//getter方法
- (NSString *)name
{return
_name;}
<2>retain (MRC)
//setter方法
- (void)setName:(NSString *)name
{if
(_name != name){
[_name release];
_name = [name retain];}}
//getter方法
- (NSString *)name
{return
_name;}
<3>copy (MRC)
//setter方法
- (void)setName:(NSString *)name
{if
(_name != name){
[_name release];
_name = [name copy];}}
//getter方法
- (NSString *)name
{return
_name;}
<4>初始化
self.firstName中调用了firstName的setter方法(setter方法中retain过一次,所以可以用此方法,效率没有直接retain的方法高)
- (KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
if (self
= [super
init]) {
self.firstName = firstName;
self.lastName = lastName;
self.phoneNumber = phoneNumber;
}
return
self;
}
或者
- (KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
if (self
= [super
init]) {
_firstName = firstName;
_lastName = lastName;
_phoneNumber = phoneNumber;
}
return
self;
}
2.
<1>伪拷贝
- (id)copyWithZone:(NSZone *)zone
{return
[self
retain];}
<2>浅拷贝
- (id)copyWithZone:(NSZone *)zone
{NSString *name =
self.name;
NSUInteger age =
self.age;
Person *Person = [[Person alloc] initWithName:name age:age];
return
Person;}
<3>深拷贝
- (id)copyWithZone:(NSZone *)zone
{ NSString *name =
self.name;
NSUInteger age =
self.age;
NSString *newName = [NSString stringWithFormat:@"%@",name];
Person *newPerson = [[Person alloc] initWithName:newName age:age];
return
newPerson;}
举例
1.
NSArray, NSDictionary, NSSet等类,会在对象加入后引用计数加一获得所有权,在对象被移除或者整个容器对象被释放的时候释放容器内对象的所有权。类似的情况还有UIView对subview的所有权关系,UINavigationController对其栈上的controller的所有权关系等等。
//push入栈显示新的视图控制器,被导航控制器管理,retain+1,如果back后被推回去,自动deallco,pop后引用计数-1
//push后detailVC进入navigationController的viewControllers数组栈中,相当于UIView中的subviews,数组销毁,里面的引用计数全部-1
[self.navigationController pushViewController:detailVC animated:YES];
//???
还有一些用法会让系统拥有对象的所有权。比如NSObject
的performSelector:withObject:afterDelay。如果有必要,需要显示的调用cancelPreviousPerformRequestsWithTarget:selector:object:,否则有可能产生内存泄露。
因这种原因产生的泄露因为并不违反任何规则,是Intrument所无法发现的。
3.@property(nonatomic,
copy)void(^PassVauleBlock)(UIImage*)
passVaule;
必须这么写
block是个代码片段要用copy
@property(nonatomic,
copy) NSMutableArray *contacts;
这么写是错的,不可变数组没有copy方法,实现的是父类不可变数组中copy的方法,copy出的是不可变数组
4.@property(nonatomic,
assign)id<UIScrollViewDelegate>delegate;
为什么delegate
用
assign
而不使用
retain (应为不用管代理的死活,代理死了不必完成协议)
一个对象没必要管理自己delegate的生命周期,或者说没必要拥有该对象,所以我们只要知道它的指针就可以了,用指针找到对象去调用方法,也就是委托实现的感觉。
或者我们换个角度,从内存管理方面也可以解释这个问题。delegate的生命周期不需要让该对象去控制,如果该对象对其使用retain很可能导致delegate所指向的对象无法正确的释放。
循环引用
所有的引用计数系统,都存在循环应用的问题。例如下面的引用关系:
对象a创建并引用到了对象b.
对象b创建并引用到了对象c.
对象c创建并引用到了对象b.
这时候b和c的引用计数分别是2和1。当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中。
这种情况,必须打断循环引用,通过其他规则来维护引用关系。比如,我们常见的delegate往往是assign方式的属性而不是retain方式的属性,赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。如果一个UITableViewController对象a通过retain获取了UITableView对象b的所有权,这个UITableView对象b的delegate又是a,如果这个delegate是retain方式的,那基本上就没有机会释放这两个对象了。自己在设计使用delegate模式时,也要注意这点。
因为循环引用而产生的内存泄露也是Instrument无法发现的,所以要特别小心。
5.@property
(nonatomic,
retain,
readonly) UILabel *contentLabel;
@property
(nonatomic,
retain,
readonly) UIButton *submitButton;
readonly没有setter方法,只有getter方法
在.m文件中//没有setter方法用不了_contentLabel,需自己创建实例变量(@synthesize
实现setter和getter的方法)
@synthesize
contentLabel = _contentLabel;
@synthesize
submitButton = _submitButton;
- (UILabel *)contentLabel{
if (!_contentLabel) {
//没有setter方法,没有内部retain,
不能加autorelease,
保存对象的地址,
在dealloc中release,这里引用计数需+1
_contentLabel = [[UILabel alloc] initWithFrame:self.view.bounds];
//如果创建对象时先getter button,lable就会遮挡button
//[self.view addSubview:_contentLabel];
//保证lable永远在最下面
[self.view insertSubview:_contentLabel atIndex:0];
}
return
_contentLabel;
}
(UIButton *)submitButton{
if (!_submitButton) {
//引用计数+1
_submitButton = [[UIButton buttonWithType:UIButtonTypeSystem] retain];
[self.view addSubview:_submitButton];
}
return
_submitButton;
}
-(void)dealloc{
[_contentLabel release];
[_submitButton release];
[super
dealloc];
}
6.//保存从数组中移除的数据要retain一次,要不然数组元素出数组内存引用计数会自动减到0,数据销毁
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath {
id object = [_datasource[fromIndexPath.row] retain];
[_datasource removeObjectAtIndex:fromIndexPath.row];
[_datasource insertObject:object atIndex:toIndexPath.row];
[object release];
}
@end
相关文章推荐
- 《实用Common Lisp编程》第三章 实践:简单的数据库
- C++函数
- 133.Oracle数据库SQL开发之 数据库对象——通用调用
- 越过65K方法数的限制编译APP
- Opencv人头跟踪检测
- “滑机约拍”--第一阶段冲刺(4)
- (第八周项目5)计数的模式匹配
- web service axis2常见异常原因
- IE6的重定向页面无法跳转解决
- 二叉排序树的基本操作(建立,中序遍历,查找,删除,插入)
- 【小程序】哈密尔顿距离
- OGNL小结
- “滑机约拍”--第一阶段冲刺(3)
- 132.Oracle数据库SQL开发之 数据库对象——重载方法
- UINavigationController+UITabBarController+内容遮挡问题
- SDUT 2498 (AOE网上的关键路径 )
- Android服务和如何使用服务
- BestCoder Round #64 (div.2)
- hdoj 数列有序
- 131.Oracle数据库SQL开发之 数据库对象——用户自定义的构造函数