您的位置:首页 > 职场人生

OC基础语法(五)---内存管理

2014-12-21 16:12 666 查看

一、基本原理
     什么是内存管理:
     移动设备的内存极其有限,每个app所能占用的内存是有限制的;
     当app所占用的内存较多时,系统会发出警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要的对象、变量等。
     管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效。
     
     栈:存放局部变量(调用完自动回收);
     堆:动态产生的存储空间(需手动回收)。    

二、对象基本结构
每个对象都有自己的引用计数器,是一个整数。表示“对象被引用多少次”,即有多少人在使用这个对象。
    
每个对象OC对象内部专门有4个字节的存储空间来存储引用计数器。

引用计数器的作用:
1.创建新对象,该计数器默认是 1;
2.当一个对象的引用计数器为0,就被回收,或退出程序;
3.给对象发送一条retain消息,可以引用计数器 +1;
4.给对象发送一条release消息,可以使计数器-1;
5.可以给对象发送retainCount消息获得当前的引用计数器值。
    
[p retain]; 引用计数器 +1。
注:retain方法返回的是对象本身。

[p release]; 引用计数器 -1。

野指针:指向僵尸对象(不可利用的内存空间)的指针。
        当引用计数器变为0,p为野指针,这时候再往下使用p指针就会出
        错。
报错:下一行再使用 [p release]; 时
经典错误:EXC_BAD_ACCESS:访问了一块坏的内存(已经被回收,已经不可用的内存),即野指针错误。
此时对象已是僵尸对象,发送retain消息还是报错。

所以要将野指针清空: p = nil;
Java中有空指针错误,但OC中不存在。
给空指针发送消息,不报错。

注:方法的基本使用
1.retain:计数器+1,会返回对象本身;
2.release:计数器-1,没有返回值;
3.retainCount:获得当前的计数器值;
4.dealloc:当一个对象要被回收的时候,就会调用
               一定要在最后调用[super dealloc].要放在最后。
    
概念:
僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用。
野指针:指向僵尸对象的指针,给野指针发送消息会报错。
空指针:没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不回报错。

多对象内存管理:
1.谁创建,谁release
如果你通过alloc、new、或者[mutable]copy来创建一个对象,那么你必须调用release或者autorelease,
换句话说,不是你创建的,就不用你去[auto]release.

2.谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release。

总结:
1.有始有终,有加就有减;
2.曾经让对象的计数器+1,就必须在最后让对象-1.

三、set方法内存管理
内存管理代码规范:

OC对象类型,应当要严谨点:例如
- (void)setBook:(Book *)book
{
    //对当前正在使用的书(旧书)做一次release
    [_book release];

    //对新书做一次retain
    _book = [book retain];
}

最严谨的set方法:
- (void)setBook:(Book *)book
{//判断是不是新传来的对象
    If( book != _book)
    {
        [_book release];
         _book = [book retain];
     }
}

基本数据类型的set方法:
- (void)setAge:(int)age
{
   _age = age;
}

dealloc方法的代码规范:(不要直接调用dealloc)
1.一定要[super dealloc],而且放在最后;
2.对当前对象(self)所拥有的其他对象做一次release。

例子: - (void)dealloc
       {
          [_book release];
          [super dealloc];                                           
        }

- @property内存管理:

.retain:生成的set方法里,release旧值,retain新值
 @property (retain)Book * book;

property参数:
1.控制set方法的内存管理:
(1)retain:release旧值,retain新值(适用于oc对象);
(2)assign:直接赋值,不做任何内存管理(默认,用于非OC对象类型);
(3)copy:release旧值,retain新值。

2.是否要生成set方法:
(1)readwrite:同时生成setter和getter的声明和实现;
 (2) readonly:只会生成getter的声明和实现。

3.多线程管理:
(1)nonatomic:性能高(一般用这个);
 (2) atomic:性能低(默认)。

4.setter和getter的名称(不改变成员变量):
@property (getter = abc,setter = setAbc:)int age;
[p setAbc:10]; 这种用法是对的;
[p setAge:10]; 这种用法是错的;

但是: p.age = 10; 
       p.abc = 10; 
都是对的。

另:一般返回BOOL类型方法名一般以 is 开头:
@property (getter = isRich)BOOL rich;
p.rich = YES;
BOOL b = p.rich;

四、循环引用
对于循环依赖关系来说:比方说A类引用B类,B类引用A类。
这种代码编译会报错,当使用@class在两个类相互声明,就不会出现编译报错。

@class:
1.@class的作用:声明仅仅告诉编译器,某个名称是一个类
      例如: @class Card;
2.开发中引用一个;类的规范:
(1)在.h文件中用@class来声明类;
(2)在.m文件中用#import来包含类的所有的东西。
3.两端循环引用的解决方案:
(1)一端用retain;
(2)一端用assign;

用@class的好处:
1.解决循环包含;
2.提高性能,头文件不会再随着包含的文件的改变而重新编译。

@class和#import的区别:

1.#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;
      @class方式只是告诉编译器在A.H文件中 B *b 只是类的声明,具体这个类里面有什么信息,这里不需要知道,等文件真正实现的时候,才会真正去查看B类中的信息;

2.如果有上百个头文件都用#import了同一个文件,或者这些文件依次被#importm,那么一旦最开始的头文件稍有改动,后面引用这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对而言,用@class就不会出现这种问题了;

3.在.m文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引用引用类。
  
五、自动释放池
ios 5.0以后
autorelease方法:
1.会返回对象本身。
2.调用完autorelease,对象的计数器值不变,
3.会将对象放到一个自动释放池中,
4.当自动释放池被销毁时,会对池子里的所有对象做一次release操作。

@autoreleasepool
{//开始代表创建了释放池
    Person *p = [[[Person alloc] init] autorelease];
 
}//结束代表了销毁释放池

autorelease的好处:
1.不用再关心对象的销毁时间;
2.不用再关心什么时候调用release。

autorelease的使用注意:

1.占用内存较大的对象不要随意使用autorelease;

2.占用内存较小的对象使用autorelease,没有太大影响;

错误写法:

1.alloc 之后调用了autorelease,有调用了release;

2.连续多次调用了autorelease。、
    
    自动释放池:

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

2.当一个对象用autorelease方法时,会将这个对象放到栈顶的释放池。

ios 5.0以前的释放池(已淘汰)

NSAutoreleasePool *pool = [[NSAutorelease alloc] init];

Person *p = [[[Person alloc] init] autorelease];

[pool release];//[pool drain];

一般可以为类添加一个快速创建对象的类方法:

+ (id)person



  Return [[[self alloc] init] autorelease];

}

系统有会默认自动回收的:

例如:  NSString *str = @"123";

注:1.系统自带的方法里面没有包含alloc 、new、copy,说明返回的对象都是release 的

    2.开发中经常会提供一些方法,快速创建一个已经autorelease过的对象;   创建对象时不要直接用类名,一般用self。

   

六、练习
/*
set方法内存管理
*/

#implement <Foundation/Foundation.h>
//与Car和Dog类组合
#implement "Car.h"
#implement "Dog.h"

@interface Student : NSobject

  int _no; //学号
  NSString *_name; //姓名
  Car *_car; //car对象
  Dog *_dog; //dog对象

}

//方法的声明
- (void)setNo:(int)no;
- (int)no;

- (void)setName:(NSString *)name;
- (NSString *)name;

- (void)setCar:(Car *)car;
- (Car *)car;

- (void)setDog:(Dog *)dog;
- (Dog *)dog;

- (void)dealloc;

@end

@implementation Student

//方法的实现
- (void)setNo:(int)no
{
   _no = no;
}
- (int)no
{
   return _no;
}

- (void)setName:(NSString *)name
{
    //判断传进来的是不是新的对象
     if(name != _name)
   {
   //回收旧姓名
      [_name release]; 
     
  //retain新姓名
      _name = [name retain];
     
   }
}

- (NSString *)name
{
  return _name;

}

- (void)setCar:(Car *)car
{
     //判断传进来的是不是新的对象
    if(car != _car)
   {
    //回收旧车
      [_car release];

   //retain新车
    
      _car = [car retain];
     
   }
}

- (Car *)car
{
   return _car;
}

- (void)setDog:(Dog *)dog
{
      if(dog != _dog)
   {
      [_dog release];
    
      _dog = [dog retain];
     
   }

}
- (Dog *)dog
{
   return dog;
}

- (void)dealloc
{
//回收每个对象
    [_name release];
    [_car release];
    [_dog release];

    [super dealloc];

}
@end

int main()
{
//创建学生对象
    Student *stu = [[Student alloc] init];
  
//创建姓名对象
    NSString *name1 = @"rose";

//创建车子对象
Car *c1 = [[Car alloc] init];
c1.wheels = 4;
 
//将c1传递给stu
stu.car = c1;
    
   stu.name  = name1; 
   

   //alloc一次,release一次
   [c1 release];
   [stu release];

   return 0;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  黑马程序员