黑马程序员-IOS-OC基础-内存管理

-------------------------------------- android培训java培训ios培训期待与您交流! ---------------------------------


iOS下内存管理的基本思想就是引用计数,通过对象的引用计数来对内存对象的生命周期进行控制。具体到编程时间方面,主要有两种方式:

1:MRR(manual retain-release),人工引用计数,对象的生成、销毁、引用计数的变化都是由开发人员来完成。

2:ARC(Automatic Reference Counting),自动引用计数,只负责对象的生成,其他过程开发人员不再需要关心其销毁,使用方式类似于垃圾回收,但其实质还是引用计数。

内存管理的基本原理
回收那些不需要再使用的内存,提高移动设备的运行速度。管理范围:任何继承NSObject的对象,对其他数据类型(int、char、float、double、struct、enum等)无效,也就是说内存管理只对对象有效。(可以理解为内存管理,管理的是堆内存中的对象)
<1>对象的基本结构(对象存储在堆内存中)
每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,既有多少人正在使用这个OC对象;每个OC对象内部专门有4个字节的存储空间来存储金庸计数器。
<2>对象的引用计数器
(1)作用:当使用alloc、new或者copy创建以个新对象的时候,新对象的引用计数器默认值是1;当一个对象的引用计数器值为0时,对象占用的内存会被系统回收,也就是如果对象的计数器不为0,那么在整个程序运行过程中,它所占用的内存就不可能被回收,除非是整个程序已经退出。
(2)操作:给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身);给对象发送一条release消息,可以使引用计数器值-1;如果想获取当前引用计数器的值,可以给对象发送retainCount消息。
<3>对象的销毁
(1)当一个对象的引用计数器值为0时,它就会被销毁,起占用的内存就会被系统回收;
(2)当对象被销毁是,系统会自动向对象发送一条dealloc消息;一般会重写dealloc方法,在对象被销毁时释放相关资源,dealloc就像对象的遗言(重写dealloc方法可以实现对象的监听);一旦重写dealloc方法,就必须调用[super dealloc],并且放在最后面调用。
(文中代码都是在关闭ARC的情况下编写的)

<span style="font-size:14px;">#import "Person.h"  
  
@implementation Person  
// 当一个Person对象被回收的时候,就会自动调用这个方法  
- (void)dealloc  
{  
    //重写dealloc方法后的输出,可以实现对象的监听  
    NSLog(@"Person被回收了");  
      
    //这里必须调用[super dealloc]且只能放在最后  
    [super dealloc];  
}  
@end  </span>
dealloc方法不要直接调用,会引发野指针错误。一旦对象被回收,它所占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)。
/* 
1.方法的基本使用 
1> retain :计数器+1,会返回对象本身 
2> release :计数器-1,没有返回值 
3> retainCount :获取当前的计数器 
4> dealloc 
* 当一个对象要被回收的时候,就会调用 
* 一定要调用[super dealloc],这句调用要放在最后面 
 
2.概念 
1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用 
2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS) 
3> 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错 
*/  
#import <Foundation/Foundation.h>  
#import "Person.h"  
int main(int argc, const charchar * argv[])  
{  
    Person *p = [[Person alloc] init];  
    //获取对象引用计数器的值  
    NSUInteger c = [p retainCount];  
     
    //[p dealloc];//dealloc方法不要直接掉用,会引发野指针错误  
    NSLog(@"%ld",c);//c == 1  
      
     
    //使对象引用计数器的值-1  
    [p release];  
    //[p release];//不能多次调用release,会引发野指针错误。  
     p = nil; //清空指针,使p变成空指针,避免出现野指针错误,  
    return 0;  
}  
/*运行结果 
 2014-04-10 13:00:05.815 7-引用计数器的操作[818:303] 1 
 2014-04-10 13:00:05.818 7-引用计数器的操作[818:303] Person被回收了 
 */  


2, Xcode的设置
<1>取消ARC的方法

打开你的工程,点击目录的工程文件,最顶端蓝色的,然后选择project下你的工程,还是蓝色那项,然后build Settings,然后往下拉,在Apple LLVM 5.0 - Language - Objective C 里有一个选项,Objective-C Automatic Reference Counting 选择NO,就可以了。

 project -> 

build Settings -> 

Apple LLVM 5.0 - Language - Objective C ->

Objective-C Automatic Reference Counting

<2>开启僵尸对象监控


基本原则

关于MRR,我总结了一句话:是你的,就是你的;不是你的,就不是你的。

虽然看上去比较废话,但揭示了MRR内存管理里的一个核心原则,“只负责你拥有的对象的生命周期”,也就是说,如果你对一个对象有所有权,那么你就要负责其回收的工作,否则,你不需要,也不能取回收你不拥有的对象。

那些对象属于“拥有”范畴呢?

1:所有使用alloc, new, copy或mutabelCopy,以及这些关键词开头的函数返回的对象,你都是拥有所有权的,也就是要负责这些对象的内存回收工作。这是iOS开发中的一种约定,所以,当你编写自己的alloc, new或copy类型的函数时,也请遵循这样的命名规范。

2:retain返回的对象,拥有所有权。例如显示调用retain函数返回的结果,或者synthesize 的retain类型的成员变量。

3:所有使用其他函数返回的对象,没有所有权。

4:返回的对象的引用,没有所有权。

5:autorelease返回的对象没有所有权。

举例说明:

//1:使用alloc函数生成,有所有权  
NSString* stringA = [[NSString alloc] init];  
stringA = @"abc";  
[stringA release];  
  
//2:使用initWithString返回对象,无所有权  
NSString* stringB = [NSString stringWithString: @"abc"];  
  
//3:显示调用retain返回对象, 拥有所有权  
NSString* stringC = [[NSString stringWithString: @"abc"] retain];  
stringC = @"def";  
[stringC release];  
  
//4:retain类型成员变量,拥有所有权  
@property (retain) NSString* stringD;  
  
@synthesize stringD = _stringD;  
  
- (void) dealloc  
{  
    [_stringD release];  
      
    [super dealloc];  
}  
  
//5:通过引用返回的对象无所有权  
NSString** stringERef = [self stringRef];  
*stringERef = @"abc";

成员变量的内存管理

上面的例子,将成员变量声明为retain并synthesize时,编译器会帮你生成对应变量的setter与getter,其中, 生成的setter形式如下:

- (void) setStringD: (NSString*) newString  
{  
    [newString retain];  
    [_stringD release];  
    _stringD = newString;  
}  
首先获得newString的所有权,然后释放掉已经拥有的_stringD(其实此对象有可能并没有被释放,取决与其release后的引用计数是否为0), 再将newString的值赋给_stringD,这样,获得了新的,释放了旧的,就完成了对象所有权的释放与获得。注意retain与release的调用顺序,避免了同一个对象赋值引起的悬空指针问题。

当成员变量声明为assign和copy时,其生成的setter形式如下:

//copy  
- (void) setStringD: (NSString*) newString  
{  
    NSString* temp = [newString copy];  
    [_stringD release];  
    _stringD = temp;  
      
}  
  
//assign  
- (void) setStringD: (NSString*) newString  
{  
    _stringD = newString;     
}  

copy类型的成员变量也是拥有所有权的,也要在dealloc函数中显式释放。而assign不是。

很多人对self.stringD = @"abc"的调用形式比较困惑; 其实,编译器会将此语句自动转换为[self.setStringD:@"abc"];

还有一点要注意,声明成员类型时只有copy,没有mutableCopy,那么,如果一个成员变量是MutableArray,且被声明成copy类型,但编译器生成的setter中调用的是copy,而不是MutableCopy,所以,向MutableArray类型的成员插入数据时就会报错,因为其保存的真正类型不是MutableArray,而是Array. 这种情况有两个解决方案:一是将改变量类型声明为retain形式; 二是手动编写setter,调用mutableCopy函数。


NSObject函数声明了dealloc函数来清理内存,所有有“retain, copy”类型成员变量的类都要实现这个函数。在其内,要调用相应对象的release函数,不要忘记在【最后】调用[super dealloc];


说到成员变量的声明周期,还要提一下IBOutlet类型的变量,默认情况下, IBOutlet对象的类型都是retain的,由于这些对象来自界面文件(xib, storyboard),所以其出事化过程无需关心,其他处理方式与普通成员变量大体相同,只有一点,需要在-  (void) viewDidUnload函数中将这些变量置空, 如下所示:

- (void) viewDidUnload  
{  
    self.outletA = nil;  
    self.outletB = nil;  
    [super viewDidUnload];  
}  

容器对象与内存管理

iOS中,容器对象对其内的对象拥有所有权,也就是说,当一个对象被插入到容器内时,其retainCount会加一,从容器内移除时,其retainCount会减一,当容器本身被release时,期内所有对象的retainCount都会减一。如下代码所示:

NSString* stringA = [[NSString alloc] init];//stringA的retainCount: 1  
NSArray* array = [[NSArray alloc] init];  
[array addObject: stringA];//stringA的retainCount:2  
[stringA release];//stringA的retainCount:1  
[array release];//retainCount: 0  

autorelease
<1>autorelease的基本用法:
(1)autorelease会将对象方到一个自动释放池(autoreleasepool)中;
(2)当自动释放池被销毁时,会对池子里面的所有对象做一次release操作;
(3)调用autorelease会返回对象本身;
(4)调用完autorelease方法后,对象的计数器不变。
<2>autorelease的好处:
(1)不用关心对象释放的时间;
(2)不用关心什么时候调用release。
<3>autorelease的使用注意:
(1)占用内存较大的对象不要随便使用autorelease
(2)占用内存较小的对象使用autorelease,没有太大的影响。
<4>自动释放池的创建

<5>使用规律
(1) 系统自带的方法里面没有包含 alloc new copy ,说明返回的对象都是被声明了 autorelease 的,不需要再release。
例如:
NSNumber *n = [NSNumber numberWithInt:100];  
NSString *s = [NSString stringWithFormat:@"jack"];  
NSString *s2 = @"rose";  

(2) 开发中经常会提供一些类方法,快速创建一个已经 autorelease 过的对象
// 创建对象时不要直接用类名,一般用self  
 + (id)person  
 {  
    return [[[self alloc] init] autorelease];  
 }  

--------------------------------------  android培训 java培训 ios培训期待与您交流! ---------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值