文档章节

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

 明天过后1
发布于 2014/10/04 16:09
字数 3323
阅读 51
收藏 0

---------------------- 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僵尸对象的监控设置。

            


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



© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 23
码字总数 36166
作品 0
佛山
私信 提问
【iOS开发干货】Objective-C基础知识点总结(一)

长文,建议跳跃选择性阅读,大约10min可以读完全文。 iOS开发 1.目录 1.个人学习建议 2.知识点整理 3.下集预告 iOS这一行,都过了这么多年,还是水分很足,没有几个愿意安安心心查资料写东西...

曹真
2017/06/21
0
0
Objecitive-C中的nil

当我学习OC的时候,我总是忍不住在对比。Actionscript是如何实现,而oc又是如何实现。这不,碰到nil的时候我发现了很大的不同之处。做个笔记。 在as中,如果将一个对象置为null,相当于空指针...

ChildhoodAndy
2013/03/21
0
3
OC学习之旅 (一) 内存管理及OC常用方法

最近学习OC,所以在博客写下自己的笔记. OC的基本: 1 id 相当于java的Object 代表任何对象. 所以id是关键字,不能用作变量!! 2 oc中使用"."符号是调用 int a =object.a 是调用的是 [object ge...

Jonson
2013/04/13
0
2
Objective-C 和 Core Foundation 对象相互转换的内存管理总结

一、非ARC的内存管理 倘若不使用ARC,手动管理内存,思路比较清晰,使用完,release转换后的对象即可。 //NSString 转 CFStringRef CFStringRef aCFString = (CFStringRef) [[NSString alloc...

木木情深
2014/02/19
0
0
一、C语言概述

说明:这个C语言专题,是学习iOS开发的前奏。也为了让有面向对象语言开发经验的程序员,能够快速上手C语言。如果你还没有编程经验,或者对C语言、iOS开发不感兴趣,请忽略 为什么iOS开发要先...

长平狐
2013/03/28
121
1

没有更多内容

加载失败,请刷新页面

加载更多

mac 下 mysql 8.0.13 安装并记录遇到的问题 以便以后查看

安装 官网mysql 下载地址 安装过程 省去 安装好之后 下载navicat 错误1 链接 遇到 mysql 2003 - Can't connect to MySQL server 错误, 解决方案 重启mysql 服务 #错误2 ERROR 1045: Acces...

杭州-IT攻城狮
27分钟前
3
0

中国龙-扬科
30分钟前
1
0
[Spring4.x]基于spring4.x纯注解的Web工程搭建

在前文中已经说明了如何基于 Spring4.x+ 版本开发纯注解的非web项目,链接如下: https://my.oschina.net/morpheusWB/blog/2985600 本文则主要说明,如何在Web项目中,"基于spring纯注解方式...

morpheusWB
59分钟前
13
0
基础编程题目集-7-13 日K蜡烛图

股票价格涨跌趋势,常用蜡烛图技术中的K线图来表示,分为按日的日K线、按周的周K线、按月的月K线等。以日K线为例,每天股票价格从开盘到收盘走完一天,对应一根蜡烛小图,要表示四个价格:开...

niithub
今天
5
0
Jenkins window 下的安装使用

1.下载:https://jenkins.io/download/ 双击安装完毕,将自动打开浏览器: http://localhost:8080 打开对应位置的文件,将初始密钥粘贴至输入框。 第一个是 安装默认的软件;第二个是 自定义...

狼王黄师傅
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部