黑马程序员-9-Objective-C学习笔记(OC内存管理)

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

1. 内存管理简介

    (1) 移动设备的内存及其有限,因此每个app运行是的内存也进行了限制(限制值不同设备不一样的标准),如果运行占据内存超标,IOS系统会将此app进行强行杀掉。

    (2) 如果app内存接近临界值会发出警告,这会影响用户体验。为了避免这个问题需要对内存进行管理 : 当一些变量,对象不需要使用内存的时候,对他们占据的内存进行及时回收。

    (3) OC中内存管理的范围 : 任何继承了NSObject的对象 (通常这些对象的产生通常都在堆中完成),对其他基本数据类型(int、char、float、double、struct、enum等,这些基本数据类型空间分配通常都在栈中完成的)无效。

    (4) 每个OC对象都会有自己的引用计数器,它是一个整数,占据着4个字节的内存空间,它存储的是某个OC对象别别人引用的次数,这个引用计数器对内存管理及其重要,是判断一个OC对象是否被回收的标志。

    a. 取消ARC

        ARC( Objective-C Automatic Reference Count)Xcode4.0之后新建的项目都是默认使用ARC,这时候系统会"半自动"帮我们管理内存。

          如果想自己管理手动内存需要对编译器做以下设置 :

          取消ARC : Buid Settings --> Objective-C Automatic Reference Count  --> 修改为NO。

    b. 开启僵尸对象监控

        Xcode默认对僵尸对象不会进行报错监控,如果想演示僵尸对象要开启Xcode僵尸对象的监控设置。

            194825_JtxW_2245946.jpg


2. 引用计数器

        由上述所知,引用计数器是一个整数(4个字节)用来存储OC对象被引用的次数.

    a. 引用计数器的特点   

        (1) 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1

        (2) 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)

        (3) 给对象发送一条release消息,可以使引用计数器值-1

        (4) 可以给对象发送retainCount消息获得当前的引用计数器值

    // Person.h
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @end   
    
    // Person.m
    #import "Person.h"
    @implementation Person
    @end
    
    // main.m
    #import "Person.h"
    int main(){
        /*
        Person *p = [Person new];
        int count = [p retainCount];
        NSLog("count = %d",count);                            // count = 1
        */
        
        Person *p = [[Person alloc] init];
        int count = [p retainCount];
        NSLog("count = %d",count);                            // count = 1
        
        [p retain];
        count = [p retainCount];
        NSLog("count = %d",count);                            // count = 2
        
        [p release];
        count = [p retainCount];
        NSLog("count = %d",count);                            // count = 1
        
        [p release];
        count = [p retainCount];
        NSLog("count = %d",count);                            // count = 0
        
        p = nil;
        return 0;
    }


    b. 野指针(僵尸对象

             (1) 如果一个指针指向了一块被回收的内存,那么这个指针成为野指针,一个指针指向一块垃圾内存,如果使用指针来操作被回收的内存后果非常严重 ,被释放内存的OC对象叫僵尸对象。

             (2) 当你用了一个指向不可预知的指针时,即使程序运行没有问题, 那也是非常危险的. 因为这个”野指针”指向的内存空间,可能是某个重要的数据或其它程序,甚至是系统的重要内存位置. 这样造成的危害是不可预知的,这个不可预知包括危害程度的不可预知和危害时间的不可预知的. 像一颗不知何时会爆的定时炸弹.

        

    c. 对象何时被销毁

        系统回收对象的标准是引用计数器的值为0.

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

        (2) 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息,但不要使用指针来直接发送dealloc消息

        (3) 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言

        (4) 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用,因为NSObject也要做一些回收内存的处理

        (5) 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)

    // Person.h
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @property int age;
    
    @end   
    
    // Person.m
    #import "Person.h"
    @implementation Person
    
    - (void) dealloc
    {
        NSLog(@"调用 dealloc 方法");
        /*
            可以在这方法内做一些回收成员变量(如果是OC对象)的操作。
        */
        [super dealloc];
    }
    @end
    
    // main.m
    #import "Person.h"
    int main(){
        Person *p = [[Person alloc] init];
        int count = [p retainCount];
        NSLog("count = %d",count);                            // count = 1
        
        [p release];                                          // 调用 dealloc 方法
        count = [p retainCount];
        NSLog("count = %d",count);                            // count = 0
        
        // 操作僵尸对象
        // [p setAge:10];                                     // message sent to deallocated instance 
        p = nil;                                              // 当对象内存被回收时候,把指针置为空防止野指针出现
        return 0;
    }


3. 内存管理的原则

    由于内存管理我们时刻需要关注计算器里面的值,那么我们就需要花大量精力来计算,为了方便而准确我们去管理内存提出一条内存管理的原则 : 

        (1) 谁创建,谁release

            如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease

            换句话说,不是你创建的,就不用你去[auto]release

        (2) 谁retain,谁release

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

     // 注意 : 向空指针发送retain 和 release 消息不回报错
     /*--------------------------------------- Person.h ---------------------------------------*/
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @property (noatomic,retain) NSString *name;                                    // 对于OC对象@property参数建议我们使用retain
    
    @end   
    
    /*--------------------------------------- Person.m ---------------------------------------*/
    #import "Person.h"
    @implementation Person
    
    - (void) dealloc
    {
        NSLog(@"调用 dealloc 方法");
        [name release];                                                            // 依照原则,对于属性的OC变量如果使用了retain就要release
        [super dealloc];
    }
    @end
    
    /*--------------------------------------- main.m ---------------------------------------*/
    #import "Person.h"
    int main(){
        Person *p = [[Person alloc] init];                                        // Person 使用了alloc
        [p setName:@"mike"];
        
        [p release];                                                              // 依照原则,使用了alloc,必须release,输出 : 调用 dealloc 方法
        
        /*                                                                        autorelease 要运行在对象释放池内
        @autoreleasepool{
        Person *p = [[[Person alloc] init] autorelease];                          // Person 使用了autorelease
        [p setName:@"mike"];
        
        }                                                                         // 运行到这一行时候,输出 : 调用 dealloc 方法
        */
        
        return 0;
    }


4. set方法内存管理

    在 OC特有语法那一章 中我们知道 @property 参数 retain 的原理,那么在这里要补充说明这样做的目的。

 /*--------------------------------------- Dog.h ---------------------------------------*/
     #import <Foundation/Foundation.h>
     @interface Dog : NSObject
     
     @property (noatomic,assing) int age;
     
     @end
 
 /*--------------------------------------- Dog.m ---------------------------------------*/
    #import "Dog.h"
    @implementation Dog
    
    @end
 
 /*--------------------------------------- Person.h ---------------------------------------*/
    #import <Foundation/Foundation.h>
    #import "Dog.h"
    @interface Person : NSObject
    
    @property (noatomic,assing) Dog *dog;                                            // 使用 assign 策略
    // @property (noatomic,retain) Dog *dog;
    /* retain 参数 : 生成的set方法如下
    - (void) setDog:(Dog *) dog
    {
        if( _dog != dog ){
            [_dog release];
            _dog = [dog retain];
        }
    }
    
    如果没有 retain 参数 : 生成的set方法如下
    - (void) setDog:(Dog *) dog
    {
        _dog = dog;
    }
    */                                    
    
    @end   
    
    /*--------------------------------------- Person.m ---------------------------------------*/
    #import "Person.h"
    @implementation Person
    
    - (void) dealloc
    {
        NSLog(@"调用 dealloc 方法");
        // [_dog release];                                                            // 依照原则,对于属性的OC变量如果使用了retain就要release
        
        [super dealloc];
    }
    @end
    
    /*--------------------------------------- main.m ---------------------------------------*/
    #import "Person.h"
    #import "Dog.h"
    
    int main(){
        Person *p = [[Person alloc] init];                                        // Person 使用了alloc
        Dog *d = [[Dog alloc] init];
        
        [p setDog:d];                                                              
        [d release];                                                              // 依照原则,使用了alloc,必须release,如果 Person 的 Dog 没有 retain 参数这时候 d 的 retainCount  = 0
        
        Dog *pd = [p dog];                                                        // 取出属性(assign策略),有与 d 对象已经提前release了,那么取出来的对象将是一个僵尸对象
        
        [pd setAge:10];                                                           // 操作僵尸对象将会引发错误 : message sent to deallocated instance
        
        [p release];                                                              // 依照原则,使用了alloc,必须release,输出 : 调用 dealloc 方法
        
        
        
        return 0;
    }


5. 循环引用

    a. 简介 和 场景

        循环引用简单来说就是对象之间相互包含 : 对象A中包含了对象B,对象B中包含了对象A。

        (1) 使用场景 

/*--------------------------------------- A.h ---------------------------------------*/
    #import <Foundation/Foundation.h>
    /*
        在使用循环引用场景中不能简单引用别的类的头文件,这样会导致编译找不到相应类的定义
        解决方法是将该类声明为 @class 
        作用是 : 就可以引用一个类,说明一下它是一个类
        
        @class和#import的区别 : 
            (1) #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息
            (2) 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了
            (3) 在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
        
    */
    // #import "B.h"                                                            
    @class B
    
    @interface A : NSObject
    
    @property (noatomic,retain) B *b;                                                    // 对象 B 使用 retain 策略
    
    @end
 /*--------------------------------------- A.m ---------------------------------------*/
     #import "A.h"
     #import "B.h"
     
     @implementation A
     
     - (void) dealloc
     {
         NSLog(@"调用了对象 A 的 dealloc 方法");
         [_b release];
         [super dealloc];
     }
     @end
     
/*--------------------------------------- B.h ---------------------------------------*/
    #import <Foundation/Foundation.h>                                                   
    @class A
    
    @interface B : NSObject
    
    @property (noatomic,retain) A *a;                                                    // 对象 A 使用 retain 策略
    
    @end
 /*--------------------------------------- B.m ---------------------------------------*/
     #import "A.h"
     #import "B.h"
     
     @implementation B
     
     - (void) dealloc
     {
         NSLog(@"调用了对象 B 的 dealloc 方法");
         [_a release];
         [super dealloc];
     }
     @end

 /*--------------------------------------- main.m ---------------------------------------*/
     #import "A.h"
     #import "B.h"
     
     int main(){
         A *a = [[A alloc] init];                                                       // a 的计数器为 1
         B *b = [[B alloc] init];                                                       // b 的计数器为 1
         
         [a setB:b];                                                                    // 由于使用 retain 策略 b  的计数器为 1 + 1 = 2  
         [b setA:a];                                                                    // 由于使用 retain 策略 a  的计数器为 1 + 1 = 2    
         
         [a release];                                                                   // a 的计数器为 2 - 1 = 1
         [b release];                                                                   // b 的计数器为 2 - 1 = 1
         return 0;
     }

        上述可看 retain 策略在循环引用中将会导致对象之间永远无法释放

    b. 循环引用的解决方案

        由于双方都使用 retain 策略,在对象 alloc 后 release 无法调用任何一方的 dealloc 方法,那么解决思路是 : 由其中一方放开 retain : 

/*--------------------------------------- A.h ---------------------------------------*/
    #import <Foundation/Foundation.h>                                                        
    @class B
    
    @interface A : NSObject
    
    @property (noatomic,assign) B *b;                                                    // 对象 B 使用 assign 策略
    
    @end
 /*--------------------------------------- A.m ---------------------------------------*/
     #import "A.h"
     #import "B.h"
     
     @implementation A
     
     - (void) dealloc
     {
         NSLog(@"调用了对象 A 的 dealloc 方法");
         // [_b release];                                                              // 使用 assign 无需release
         [super dealloc];
     }
     @end
     
/*--------------------------------------- B.h ---------------------------------------*/
    #import <Foundation/Foundation.h>                                                   
    @class A
    
    @interface B : NSObject
    
    @property (noatomic,retain) A *a;                                                    // 对象 A 使用 retain 策略
    
    @end
 /*--------------------------------------- B.m ---------------------------------------*/
     #import "A.h"
     #import "B.h"
     
     @implementation B
     
     - (void) dealloc
     {
         NSLog(@"调用了对象 B 的 dealloc 方法");
         [_a release];
         [super dealloc];
     }
     @end

 /*--------------------------------------- main.m ---------------------------------------*/
     #import "A.h"
     #import "B.h"
     
     int main(){
         A *a = [[A alloc] init];                                                       // a 的计数器为 1
         B *b = [[B alloc] init];                                                       // b 的计数器为 1
         
         [a setB:b];                                                                    // 由于使用 assign 策略 b  的计数器为 1
         [b setA:a];                                                                    // 由于使用 retain 策略 a  的计数器为 1 + 1 = 2    
         
         [a release];                                                                   // a 的计数器为 2 - 1 = 1
         [b release];                                                                   // b 的计数器为 1 - 1 = 0 
                                                                                            // b 对象回收调用 dealloc 方法 同时 release a
                                                                                            // 输出结果 : 
                                                                                            // 调用了对象 B 的 dealloc 方法 
                                                                                            // 调用了对象 A 的 dealloc 方法  
         return 0;
     }

    c. 总结

            在相互引用场景中其中一方使用 assign 另外一方使用 retain


6. autorelease

    a. 简介

          (1) 内存管理代码占据了我们代码的1/3,为了方便管理对象内存 和 提高代码清晰度,引入的对象的自动释放 : 在对象创建好之后立刻放入对象释放池,等对象释放池被销毁的时候对象释放池会把池子里面的对象一个个拿出来对它们进行 release。

         (2) 特点 :

            1) 给某个对象发送一条autorelease消息时,就会将这个对象加到一个自动释放池中

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

            3) 调用autorelease方法时并不会改变对象的计数器,并且会返回对象本身

            4) autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release

    b. 简单使用

        在程序运行过程中,可以创建多个自动释放池,它们是以栈的形式存在内存中,OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)

            (1) ios 5.0后

                @autoreleasepool

                {

                    // ....

                }

            (2) ios 5.0前

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

                // .....

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

/*--------------------------------------- Person.h ---------------------------------------*/
    #import <Foundation/Foundation.h>

    @interface Person : NSObject
    
    @end   
    
    /*--------------------------------------- Person.m ---------------------------------------*/
    #import "Person.h"
    @implementation Person
    
    - (void) dealloc
    {
        NSLog(@"调用 dealloc 方法");
        [super dealloc];
    }
    @end
    
    /*--------------------------------------- main.m ---------------------------------------*/
    #import "Person.h"
    
    int main(){
        @autoreleasepool{
            Person *p = [[[Person alloc] init] autorelease];                                        
        }                                                                                    // 输出结果 : 调用 dealloc 方法
        
        
        
        return 0;
    }

    c. 编程中的一些约定规范       

            (1) 一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease

            (2) 比如下面的对象都已经是autorelease的,不需要再release

                NSNumber *n = [NSNumber numberWithInt:100];

                NSString *s = [NSString stringWithFormat:@"jack"];

                NSString *s2 = @"rose";

    d. 总结

        (1) alloc、new或copy的方法创建的对象都要把它们放进对象释放池

        (2) 一般来说除使用了(1)中的方法之外来创建对象的方法都是autorelease对象   

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

 详情请查看:http://edu.csdn.net/heima



转载于:https://my.oschina.net/u/2245946/blog/324203

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值