黑马程序员-OC加强回顾-内存管理

来源:互联网 发布:大疆精灵4pos数据导出 编辑:程序博客网 时间:2024/05/14 18:03

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、内存管理的基本概念及范围

 1、  由于移动设备的内存极其有限,所以每个app占用的内存也是有限制的。

2、OC内存管理的范围:

    管理任何继承NSObject的对象,对其他的基本数据类型无效。

基本数据类型数据占用空间一般在栈区,而对象类型是程序运行过程中动态分配的,存储在堆区

内存管理主要是对堆区中对象的内存管理。


二、内存管理的原理及分类

1、内存管理的原理


1)对象的所有权

  任何对象都可能有一个或多个所有者。

  只要对象至少还拥有一个所有者,它就会继续存在。

2)、引用计数器:

每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。

   对象刚被创建时,默认计数器值为1。当计数器为0时,对象被销毁。

3)、引用计数器的作用:

    判断对象要不要回收的唯一依据。

但是如果对象值为nil时,计数器为0,但不回收。

4)、引用计数器的操作

给对象发送消息,进行相应地计数器操作。

retain消息:使计数器+1,该方法返回对象本身

release消息:使计数器-1(并不代表释放对象)

retainCount消息:获得对象当前的引用计数器值      %ld  %tu

5)对象的销毁

 当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。

当对象销毁的时,系统会自动想对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关资源,dealloc就像是对象的“临终遗言”。

    一旦对象被回收了,那么它所占的存储空间就不能再使用,坚持使用会导致系统崩溃(野指针)。


2、OC内存管理分类

Objective-C提供了三种内存管理方式:

MRC    ARC   垃圾回收

需要理解MRC,但实际使用时尽量ARC


三、手动内存管理(MRC)

1、   要使用手动内存管理,首先要关闭ARC

2、重写dealloc方法

1)一定要[super dealloc],而且要放到最后

意义:先释放子类占用的空间再释放父类占用的空间

2)对self(当前)拥有的其他对象做一次release操作


- (void)dealloc{    //1 先释放子类自己的对象的空间    NSLog(@"Person已经挂了");    //2 再释放父类的    [super dealloc];}



四、内存管理的原则

1、内存管理的原则


1)原则

  只要还有人在使用某个对象,那么这个对象就不会被回收

2)谁创建,谁release

3)谁retain,谁release

4)曾经让某个对象计数器加1,就应该让其在最后减1 。


2、内存管理研究内容

1)野指针:  

(1)定义的指针变量没有初始化    

(2)指向的空间已经被释放了

 

    2)内存泄露: 

   

            

 {             Person *p  = [Person new];             }            p 栈区             [Person new];  堆区

 

             如果栈区的p已经释放了,而堆区的空间还没有释放,堆区的空间就被泄露了

五、单个对象内存管理(野指针)

野指针:访问一块坏得内存(已经被回收的,不可用的内存)

僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用。(默认状态下,Xcode为了提高编码效率,不会时时检查僵尸对象。)

注意:

1)空指针:没有指向任何东西的指针,给空指针发送消息并不会报错。

nil   Nil   NULL的区别

nil:指向OC对象

Nil:指向OC类

NULL:通用指针(泛型指针)

2)不能用[p retain]让僵尸对象复活。

3)也指针操作,一个指针指向的空间被释放,这个指针就成了僵尸对象,不能继续操作僵尸对象。


六、单个对象内存管理

1)避免使用僵尸对象的方法:在释放了对象指向的空间后,设置对象为nil。


2)对象的内存泄露

(1)retain和release个数不匹配,导致内存泄露。


(2)对象使用过程中被赋值了nil,导致内存泄露。


 

Dog *d = [[Dog alloc] init];   //1        d = nil;                [d eat];  //nil eat        [d release]; // nil release


(3)在函数或者方法中不当的使用retain或者release造成的问题


   

-(BOOL)compareColorWithOther:(Dog*)dog{    [dog retain];  //让传入的对象的引用+1        return YES;


七、多个对象内存管理

1、多个对象的野指针问题(知识点)

有两个类,存在关联关系,Car的对象是Person对象的实例变量。

Person类的声明#import <Foundation/Foundation.h>#import "Car.h"@interface Person : NSObject{    Car *_car;  //实例变量,和Person是存在关联关系}-(void)goLasa;-(void)setCar:(Car *) car;@endPerson类的实现#import "Person.h" @implementation Person - (void)dealloc{    NSLog(@"人已经挂了");    [super dealloc];} -(void)goLasa{      [_car run]; }-(void)setCar:(Car *) car{         _car = car;   }@endCar类的声明#import <Foundation/Foundation.h> @interface Car : NSObject{    //定义实例变量    int _speed;}//声明一个对象方法-(void)run;-(void)setSpeed:(int) speed;@endCar类的实现#import "Car.h" @implementation Car- (void)dealloc{    NSLog(@"车已经毁了");    [super dealloc];}-(void)setSpeed:(int) speed{     _speed = speed; }-(void)run{     NSLog(@"车以%d码的速度奔向拉萨",_speed); }@end


 

问题是:

在主函数中分别创建Person类和Car类的对象,但是如果先释放Car的对象,那么Person类的对象的中的实例变量_car就成了野指针,不能再调用run方法。                 

 

2、多个对象的内存泄露问题

在上面的例子中,为了避免野指针操作,可以改进代码。

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

    

 _car = [car retain];   

}

这样可以避免野指针操作,但是去无法释放car对象,造成了内存泄露

 

3、多个对象的内存管理

解决办法:

改进dealloc方法,让Person释放之前,先释放car。

 

- (void)dealloc{    [ _car release];     NSLog(@"人已经挂了");    [super dealloc];}



八、set方法内存管理

set方法的内存管理的原则

如果在一个类中,有其他类的对象(关联关系)

set方法书写的时候,要先判断是否是同一个对象,如果不是,先release旧值,在retain新值。

1、原对象无法释放造成的内存泄露

如果一个Person对象和两个Car对象有关联关系,其中一个可以释放,而另一个无法释放的问题。

Person *p = [Person new];        Car *bmw = [Car new];        bmw.speed = 100;        p.car = bmw;        [p driveCar];                       Car *byd = [Car new];        byd.speed = 80;        p.car = byd;        [p driveCar];        [byd release];        [p release];结果:2015-11-10 15:00:32.742 9-set方法的内存管理[780:63952] 车正在以100的速度往前开!2015-11-10 15:00:32.743 9-set方法的内存管理[780:63952] 车正在以80的速度往前开!2015-11-10 15:00:32.743 9-set方法的内存管理[780:63952] 速度为80的car dealloc!2015-11-10 15:00:32.743 9-set方法的内存管理[780:63952] Person dealloc!Program ended with exit code: 0


 

第一个car没有释放,造成内存泄露。

 

2、原对象可以释放,但是在set自己的时候又出现僵尸对象访问。

修改set方法,先释放上一个car对象,在赋值新的。

-(void)setCar:(Car *) car{      //为了解决set方法的内存泄露,我们在新的retain之前,先release旧值   [_car release];   _car = [_car retain];}


 

 

3、判断新传过的对象是否是原来对象,如果不是,先release,再retain。

if (_car != car) {                [_car release];          _car = [car retain]; 

 

总结:

1、对于基本类型数据作为实例变量

int _speed;    set方法的写法     -(void)setSpeed:(int)speed{         _speed = speed;    }


2、对于对象作为另外一个类的实例变量

-(void)setDog:(Dog*)dog{  //1)判断对象是否是原对象        if(_dog != dog){            //2) release旧值           [_dog release];             // retain 新的值,并且赋值给实例变量           _dog = [dog retain];        }      }



九、@property参数 

1、@property的修饰关键字

1)控制set方法的内存管理

(1)retain:release旧值,retain新值,要配合nonatomic使用,用于OC对象。

@property(nonatomic,retain) Car *car;-(void)setCar:(Car *)car{      if(_car != car){        [_car realase];       _car = [car retain];     } }


(2)assign:直接赋值,不做任何内存管理(默认,用于非OC对象类型)。


assign 直接赋值 -(void)setCar:(Car *)car{      _car = car;     }

(3)copy:release旧值,copy新值(一般用于NSString *)。


2、控制是否需要生成set方法

1)readwrite:同时生成set方法和get方法(默认)。

2)readonly:只会生成get方法。

@property (nonatomic,assign,readonly) int tuiNum;




3、多线程管理

1)atomic:性能低(默认),对属性加锁,多线程下线程安全。

2)nonatomic:性能高(为iOS系统开发软件建议使用,为mac开发软件可以使用atomic)。

    对属性不加锁,多线程下不安全。


4、控制set方法和get方法的名称

1)setter:设置set方法的名称,一定有个冒号。

2)getter:设置get方法的名称。

@property (nonatomic,assign,setter=isVip:,getter=isVip) BOOL vip;[p isVip:YES];  //调用set方法                       if (p.isVip)    //调用get方法       {            NSLog(@"这时VIP客户");        }




十、@class用法

1、为什么要使用@class

可以简单的引用一个类,只是告诉编译器这是一个类,并不会包含类里的所有内容。

好处:如果引用的类内部发生改变,而不需要重新编译,可以提高效率。

2、具体用法

在.h文件中使用@class引用一个类

在.m文件中使用#import包含这个类的.h文件

如在A类中引用B类。

代码如下:

A.h文件中 @class BA.m文件中#import “B.h”



3、特殊用法:两个类互相引用。

一个用#import引用,另一个用@class引用。


4、面试题

#import和@class的区别

1、作用上的区别

1)#import会包含引用类的所有信息(内容),包括引用类的变量和方法。

2)@class仅仅是告诉编译器有这么一个类,不包括类里地具体信息。

2、效率上的区别

如果很多头文件都通过#import引用了同一个类,当这个类发生改变的时候,引用它的所有类都要重新编译一遍,效率非常低。但是使用@class就不会这样,所以效率相对较高。


十一、循环retain问题

1、循环retain的场景 

比如A对象retain了B对象,B对象retain了A对象 循环retain的弊端 这样会导致A对象和B对象永远无法释放 

有Dog和Person两个类Dog类的声明#import <Foundation/Foundation.h>@class Person;@interface Dog : NSObject@property (nonatomic,retain) Person *owner;@endPerson类的声明#import <Foundation/Foundation.h>@class Dog;@interface Person : NSObject//人拥有一条狗@property (nonatomic,retain) Dog *dog;@end主函数int main(int argc, const char * argv[]) {    @autoreleasepool {                Person *p = [Person new];  //1        Dog *d = [Dog new];   //1                //人有一条狗        p.dog = d;           d.owner = p;                  [p release];          [d release];      }    return 0;}结果是p和d都没有释放。



2、循环retain的解决方案 

当两端互相引用时,应该一端用retain、一端用assign 


十二、NSString类的内存管理问题

1、NSString类的内存特点


使用字符串的时候:@“” stringWithString   alloc initWithString  这三种格式都是在常量区

如果你需要的字符串在常量去已经存在了,就不会再分配新的内存空间

还有其他几种格式会存在堆区。

对于字符串的引用计数器是一个非常大值,无法用一个release就释放掉其内存空间。


十三、autorelease基本使用

1、自动释放池

一种特殊的栈结构

2、自动释放池的创建方式

@autorelease{   //自动释放池的开始代码块;}    //销毁自动释放池



3、autorelease

是只用支持引用计数的内存管理方式

它会在自动释放池销毁的时候对池子里的每个对象发送一次release消息。

注意:

这里只是发送一次release消息,如果对象的引用计数还不为0,则对象依然不会被释放。

4、使用autorelease的好处


1)不需要关心对象释放的时间

2)不需要关心什么时候调用release。


5、autorelease基本用法


1)将对象放到一个自动释放池中

2)当自动释放池销毁时,会对池子里的所有对象发送一次release消息

3)会返回对象本身

4)调用完autorelease方式后,对象的计数器不受影响。


int main(int argc, const char * argv[]) {    //1 创建自动释放池    Person *p = [Person new];  // p  1    @autoreleasepool {//自动释放池开始                [p run];         NSLog(@"%lu",p.retainCount); // 1                // [p autorelease] 把对象p加入到自动释放池中        // 注意:加入到自动释放池中以后, 引用计数不会变化        [p autorelease];  //加入自动释放池,        NSLog(@"%lu",p.retainCount); // 1                [p run];            }//自动释放池结束   [p release];    [p run];  //此时就是僵尸对象访问了,会报错。    return 0;}




6、autorelease的原理

autorelease只是把release的调用延迟了,当自动释放池销毁的时候,池子里地对象才会调用release方法。


0 0
原创粉丝点击