码迷,mamicode.com
首页 > 移动开发 > 详细

【点杀ios】内存管理

时间:2015-08-20 01:17:52      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:

1.为什要进行内存管理
ios中,每个应用程序所能占有的内存是有限制的。如果你的应用程序所占用的内存超过了这个限制,系统会给你发出内存警告。如果警告后你的程序依然继续占用过多内存,那么系统有可能强制关闭你的应用程序(闪退)。并且,如果内存管理没做好,对程序的流畅度也会产生很大的影响。所以,我们在开发应用程序的过程中,要进行内存管理。

2.对什么进行管理:

内存分为堆内存和栈内存。栈内存的分配和释放是系统做的,程序员无法参与。而堆内存,是程序员进行管理的。分配和释放是程序员完成的。
任何继承了NSObject的对象都需要进行内存管理(存放在堆内存),基本数据类型不需要进行管理(存放在栈内存,自动释放。)
 
例如:有A,B两个整型变量,和P对象。A中存放的是4,B中存放的是12,P中存放的是对象的地址ff1a
技术分享
当函数结束后,A,B变量会被自动释放。指针变量p也会被自动释放。但是,存放在堆内存中的对象本身却不会被释放。这样就会导致内存泄露。
技术分享
在java语言中,会有一个线程反反复复的遍历内存,如果发现有对象不被指针所指,便会将其回收掉。但是在OC中,不存在这样的机制。那么,OC中是如果对内存进行管理的呢。我们先一起看看OC对象的内部构造。
 
3.OC对象的内部构造和引用计数器
每一个对象都有自己的引用计数器,是一个4字节的整数,用来记录对象本身被多少人使用。当对象被创建的时候,引用计数器为1,每被引用一次,计数器加1。当引用计数器为零的时候,对象会被回收。
isa指针,是对象指向自己所属类的一个指针。
技术分享

(1)对引用计数器的操作
给对象发送retain消息,可以使引用计数器加1.    retain方法返回的是对象本身。
给对象发送release消息,可以使引用计数器-1.
retainCount消息,可以获得当前引用计数器值。
(2)销毁对象
当对象的引用计数器为0的时候,系统会回收该对象。
当对象被销毁时,系统会自动向对象发送dealloc消息。
一般我们会重写dealloc方法,在对象销毁前做一些事情。但一定要在最后加【super dealloc】。
(3)例子1
好,让我们一起来写一段代码。创建好工程后,记得关闭ARC (设置成NO)
技术分享
 
创建一个Student类,对Student对象做内存管理,并在Student的实现方法中,重写dealloc方法,一旦对象被释放,就会调用该方法。
Student.m文件如下:
#import "Student.h"

@implementation Student
- (void)dealloc
{
    NSLog(@"student对象被释放");
    [super dealloc];
}
@end

main.m文件如下:

#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
    //创建student对象
    //alloc创建对象后,对象的引用计数器为1
    Student* student = [[Student alloc]init];
    
    //查看此时的对象引用计数器是多少
    NSLog(@"%ld",[student retainCount]);
    return 0;
}

运行结果如下:

2015-08-19 14:48:26.320 01-引用计数器的基本使用[649:41159] 1

当我们使用release对引用计数器减1之后:

main.m文件如下:

#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
    //创建student对象
    //alloc创建对象后,对象的引用计数器为1
    Student* student = [[Student alloc]init];
    
    [student release];
    
    return 0;
}

运行结果如下:

2015-08-19 14:50:48.079 01-引用计数器的基本使用[659:42849] student对象被释放

我们看到引用计数器为0,对象被销毁了。好,我们再试着修改main.m

#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
    //创建student对象
    //alloc创建对象后,对象的引用计数器为1
    Student* student = [[Student alloc]init];
    
    //对引用计数器加1
    [student retain];
    NSLog(@"%ld",[student retainCount]);
    return 0;
}

运行结果:

2015-08-19 14:54:30.940 01-引用计数器的基本使用[677:45062] 1
2015-08-19 14:54:30.941 01-引用计数器的基本使用[677:45062] 2
通过上述例子我们知道了,alloc之后,对象的引用计数为1,ratain方法可以让引用计数器+1。release方法可以让对象引用计数器减1 。当对象的引用计数器为0的时候,对象会被释放。对象被释放前,会调用dealloc方法。
并且,我们还得出一个结论,那就是,只要alloc啦,就必须release。只要ratain了,就必须release。否则对象的引用计数器无法达到平衡。会导致不使用的对象引用计数器依然不为0,从而造成内存泄露。
 
4.僵尸对象
 
技术分享
当我们release之后,引用计数器为0了,对象会被释放掉。但是指向对象的指针p依然指向原来的堆内存。此时,这个指针称之为野指针。也就是它所指的对象已经不能使用了(僵尸对象)。此时,再使用p指针向对象发送消息,会报错。
为了避免野指针的出现,对象释放后,要清空指针。p = nil;
 
5.类内部的内存管理
什么是类内部的内存管理,语言是苍白的,我说再多也没有代码说的明白。接下来,我会通过多个例子,一步一步循序渐进的向大家阐述。
例1:
我们创建了两个类Student类和Book类。类的关系是Student拥有一本书。也就是Student的内部有一个属性是Book类型的。他们之间的引用关系大致如下图:
技术分享
接下来,我们通过代码的形式实现它
student.h文件如下:
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Student : NSObject
{
    Book* _book;
}
//set方法
-(void)setBook:(Book*)book;
//get方法
-(Book*)book;
@end

student.m文件如下:

#import "Student.h"

@implementation Student
//set方法实现
-(void)setBook:(Book *)book
{
    _book = book;
}
//get方法实现
-(Book*)book
{
    return _book;
}


//dealloc方法
- (void)dealloc
{
    NSLog(@"学生对象被释放");
    [super dealloc];
}
@end

Book.h不变Book.m文件如下:

#import "Book.h"

@implementation Book
- (void)dealloc
{
    NSLog(@"书被释放");
    [super dealloc];
}
@end

运行结果如下:

2015-08-19 16:46:03.951 02-类内部的内存管理1[925:97891] 学生对象被释放
2015-08-19 16:46:03.952 02-类内部的内存管理1[925:97891] 书被释放

我们发现book和student都被释放了,这是为什么呢?书本明明被引用了两次(alloc和setBook)。release一次。但也被释放了。我们观察发现,这是因为student没有对book做retain操作。所以,我们在student的set方法中,补充上retain操作。

student.m文件修改成下面这样:

#import "Student.h"

@implementation Student
//set方法实现
-(void)setBook:(Book *)book
{
    _book = [book retain];
    
}
//get方法实现
-(Book*)book
{
    return _book;
}


//dealloc方法
- (void)dealloc
{
    NSLog(@"学生对象被释放");
    [super dealloc];
}
@end

运行结果如下:

2015-08-19 16:49:41.920 02-类内部的内存管理1[936:99847] 学生对象被释放

看似正确了,但总觉的哪里不对,是的,相信你的直觉,我们一起来画图看看。

技术分享

此时我们发现。明明没有指针指向book对象了,但book对象的引用计数器依然为1 。这是为什么呢?答案很简单。估计你也猜到了。我们在set方法中给book做了retain。但是没有对其做release操作。那么,什么时候做release操作呢?当然是student不用book的时候啦,那么,什么时候student不用book呢。呵呵,student销毁的时候啊,现在你直到在哪里加release了吧。对,就是在student的dealloc方法中。代码如下:
#import "Student.h"

@implementation Student
//set方法实现
-(void)setBook:(Book *)book
{
    _book = [book retain];
    
}
//get方法实现
-(Book*)book
{
    return _book;
}


//dealloc方法
- (void)dealloc
{
    NSLog(@"学生对象被释放");
    
    [_book release];
    [super dealloc];
}
@end
OK,你觉的上述代码完美了么?其实差的远了。还有很多bug等着我们去修改呢,接下来,我们重新创建一个程序。
同样有Student和Book两个类。
例2:
student.h代码如下:
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Student : NSObject
{
    int _age;
    Book* _book;
}
//book的set和get方法
-(void)setBook:(Book*)book;
-(Book*)book;

//age的get和set方法
-(void)setAge:(int)age;
-(int)age;
@end

student.m代码如下:

#import "Student.h"

@implementation Student
-(void)setBook:(Book *)book
{
    _book = [book retain];
}
-(Book*)book
{
    return  _book;
}

-(void)setAge:(int)age
{
    //基本数据类型,不必做内存管理
    _age = age;
}
-(int)age
{
    return  _age;
}

- (void)dealloc
{
    [_book release];
    
    NSLog(@"学生对象被销毁了");
    [super dealloc];
}
@end

book.h代码如下:

#import <Foundation/Foundation.h>

@interface Book : NSObject
{
    //页数
    int _page;
}

//页数的set和get方法
-(void)setPage:(int)page;
-(int)page;
@end

book.m代码如下:

#import "Book.h"

@implementation Book
-(void)setPage:(int)page
{
    _page = page;
}
-(int)page
{
    return  _page;
}

- (void)dealloc
{
    
    NSLog(@"书被销毁了");
    [super dealloc];
}
@end

运行结果:

2015-08-19 19:30:07.608 03-Set方法内存管理[1231:149504] 学生对象被销毁了
2015-08-19 19:30:07.609 03-Set方法内存管理[1231:149504] 书被销毁了

看似很完美是吧,一本书,一个学生,书alloc一次,ratain一次。release两次。学生alloc一次。release一次。完全没有问题。别着急,看看我将主函数改成这样会如何:

main.m:

#import <Foundation/Foundation.h>
#include "Student.h"
#include "Book.h"
int main(int argc, const char * argv[]) {
    
    Student* s = [[Student alloc]init];
    Book* b = [[Book alloc]init];
    
    //将书给学生
    [s setBook:b];
    
    //又创建了一本书b1
    Book* b1 = [[Book alloc] init];
    //学生将书b换成了b1
    [s setBook:b1];
    
    //alloc后要release  //新创建的b1 也做了release
    [s release];
    [b release];
    [b1 release];
    return 0;
}

以上代码要表达的意思很简单,就是这个学生换了一本新书:

运行结果:

2015-08-19 19:38:10.378 03-Set方法内存管理[1269:154354] 学生对象被销毁了
2015-08-19 19:38:10.379 03-Set方法内存管理[1269:154354] 书被销毁了

明明有两本书,却只被销毁了一本。这是什么问题导致的呢?观察代码后,我们发现。我们在给学生换书的时候调用了set方法。而set方法中仅仅对新书做了retain操作,并没有对旧书做release操作。(以前对旧书做release操作,是因为Student对象销毁的时候在dealloc方法中做的,但现在是换书,并没有销毁student对象)。所以,我们要在set方法中判断新书旧书,对新书retain,对旧书release,代码如下:

重新修改后的set方法:

@implementation Student
-(void)setBook:(Book *)book
{
    //如果新书不等于旧书,说明是换书
    if (_book !=book)
    {
        //对旧书做release
        [_book release];
        _book = [book retain];
    }
    
}

运行结果如下:

2015-08-19 19:49:28.347 03-Set方法内存管理[1288:157711] 学生对象被销毁了
2015-08-19 19:49:28.348 03-Set方法内存管理[1288:157711] 书被销毁了
2015-08-19 19:49:28.348 03-Set方法内存管理[1288:157711] 书被销毁了

金星老师:完美!!!!

10.内存管理代码规范

(1)只要调用了alloc,就必须release
(2)set方法规范
  -》基本数据类型,直接赋值  _age=age;
  -》oc对象类型
  先判断是不是新对象(传进来的和现有的是否一致)
  if(car!=_car)
  {
    对旧对象做一次release
    [_car release];
    对新对象做一次retain
    _car = [car retain]
  }
 
 (3)dealloc代码规范
  一定要调用[super dealloc]放到最后面
  对当前对象所拥有的其他对象做release。
 
好的,这样做,完美是完美了,但有个问题,太TMD麻烦了,这样写代码,项目庞大了以后,不得累死啊。对象没销毁,先把自己销毁了。那肿么办,别着急,听我漫漫道来。
7.@property
 
我们知道,@property可以帮我们实现set和get方法,所以我们定义属性常常这样定义
@Property  NSString* name;
那么,这样的属性,set和get方法是什么样的呢?其实它们是最简单的直接赋值方式
-(void)setName:(NSString*)name
{
  _name_name;
}
我们知道,这样的直接赋值方式是十分不严谨的,所以我们将set方法改造成了下面这样:
-(void)setName:(NSString*)name
{
   if(_name != name)
  {
    [_name release];
    _name = [name retain];
  }
}
那么,有没有一种方式,能自动将set方法生成第二种形式的呢。当然有,就是在property的时候使用retain关键字
@Property(ratain) NSString* name
按照上面这种方式定义属性,就会生成和第二种一模一样的set方法。
那么Property还可以放哪些参数呢?我们一起来看看:
 
 reatain:适用于OC对象类型,release旧值,ratain新值
 assign:默认,适用于基本数据类型,直接赋值
 copy    :release旧值,copy新值(http://www.cnblogs.com/hellomeng/p/4732437.html)
 readonly:只读,只会生成get方法,不生成set方法
 readwrite:默认,可读可写,生成set和get
 nonatomic:生成set方法的时候,不要加多线程锁(性能高)
 atomic:默认,加入多线程(性能低)
 
8.自动释放池 
好了,解决了set方法的问题了,但是我们发现,每次创建对象都要release操作,还是很麻烦了,难道创建100个对象,我们就要写100次release么。当然不用。现在,我们祭出oc的另外一个杀器。自动释放池
 
在主函数中有这样一段代码
@autoreleasepool
{ //创建池子
 
  写在次部分的代码,在自动释放池销毁的时候,会对池子内的所有对象做一次release操作。这相当于什么?想想之前我们在主函数中干什么了?对了,相当于帮我们做
  了release。以后我们创建对象后,就不用欠了吧唧的再写release啦。但是请注意,这里说的是release。自动释放池释放,只是对池子里的对象做了release操作而
  已,并不是释放了对象。至于里面的对象释放与否,还要看对象内部的引用计数器是否为0才行。
 
}//释放池销毁
 
释放池里也可以嵌套释放池。系统已栈的形式存放释放池。先创建的释放池后释放
 
注意:
   如果对象占用内存较大的对象,不要随笔使用autorelease,会长时间占用资源
 
那么问题由来了,alloc之后的release有人替我们做了,set方法有人替我们写了,dealloc有没有人替我们做呢。哈哈。接下来,就是oc中内存管理的boss闪亮登场的时候了。它就是大名鼎鼎的ARC。
 
9:ARC
ARC是编译器特性(并不是垃圾回收机制),会自动判断我们创建的对象什么时候要进行release操作,那么,它是怎么直到我们什么时候要release的呢?ARC有以下几个准则
ARC自动释放准则:
(1)只要没有强指针指向对象,便自动释放。(默认下,所有指针都是强指针。声明强指针__strong,生命弱指针__weak)
(2)只要发现@property参数是retain,便会自动在dealloc中加release。
(3)alloc对象后,会自动帮我们添加release
 
ARC特点:
(1)代码证不再允许我们调用release,retain方法。
(2)允许重写delloc方法,但不允许调用super dealloc
(3)property定义属性时,括号中用strong增加的是强指针,用weak增加的是弱指针。
 
一句话概括就是,ARC自动帮我们做了release的操作。今后我再也不必在代码的任何位置写release了。
什么?怎么创建ARC程序?
不需要进行任何额外的操作,默认情况下,你创建的程序就是ARC程序。
 
 
 

【点杀ios】内存管理

标签:

原文地址:http://www.cnblogs.com/hellomeng/p/4743826.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!