文档章节

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

 明天过后1
发布于 2014/10/04 16:09
字数 3323
阅读 51
收藏 0
点赞 0
评论 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
博文 20
码字总数 36166
作品 0
佛山
【iOS开发干货】Objective-C基础知识点总结(一)

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

曹真 ⋅ 2017/06/21 ⋅ 0

一、C语言概述

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

长平狐 ⋅ 2013/03/28 ⋅ 1

Objecitive-C中的nil

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

ChildhoodAndy ⋅ 2013/03/21 ⋅ 3

OC学习笔记四 数据类型

Objective-C 的基本数据类型为 int float double char 1.int类型 数据常量由一个或多个数字的系列组成,系类前的负号表示该值是一个负值 2.float类型 float类型可以存储包含小数位的值,要区...

卧龙小 ⋅ 2014/09/22 ⋅ 0

OC学习之旅 (一) 内存管理及OC常用方法

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

Jonson ⋅ 2013/04/13 ⋅ 2

Objective-C 和 Core Foundation 对象相互转换的内存管理总结

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

木木情深 ⋅ 2014/02/19 ⋅ 0

OC的内存分配

1.程序占用内存分类 栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值,基本数据类型等。 堆区:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 ,OC中用all...

JlongTian ⋅ 2016/01/04 ⋅ 0

一、Objective-C概述

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

长平狐 ⋅ 2013/03/28 ⋅ 0

iOS引用转换:Foundation与Core Foundation对象互相转换(__CFNSString转NSString,void *转id等等)

1. 两个框架的基本知识 1.1 Foundation 框架名是,在Xcode新建工程时可以选择导入(其实会默认自动依赖好)。Foundation框架允许使用一些基本对象,如数字和字符串,以及一些对象集合,如数组,...

陈满iOS ⋅ 05/10 ⋅ 0

从现在,开始swift

导语 苹果在2014的WWDC中推出了Swift 这一全新的编程语言,一时之间成为各大网站的头条和所有程序员的讨论热点,与可读性较差的Objective-C相比,这门融合了多种编程语言优点的语言得到了各大...

芳仔小脚丫 ⋅ 2014/10/15 ⋅ 4

没有更多内容

加载失败,请刷新页面

加载更多

下一页

tcp/ip详解-链路层

简介 设计链路层的目的: 为IP模块发送和接收IP数据报 为ARP模块发送ARP请求和接收ARP应答 为RARP模块发送RARP请求和接收RARP应答 TCP/IP支持多种链路层协议,如以太网、令牌环往、FDDI、RS-...

loda0128 ⋅ 56分钟前 ⋅ 0

spring.net aop代码例子

https://www.cnblogs.com/haogj/archive/2011/10/12/2207916.html

whoisliang ⋅ 今天 ⋅ 0

发送短信如何限制1小时内最多发送11条短信

发送短信如何限制1小时内最多发送11条短信 场景: 发送短信属于付费业务,有时为了防止短信攻击,需要限制发送短信的频率,例如在1个小时之内最多发送11条短信. 如何实现呢? 思路有两个 截至到当...

黄威 ⋅ 昨天 ⋅ 0

mysql5.7系列修改root默认密码

操作系统为centos7 64 1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1 这一行配置让 mysqld 启动时不对密码进行验证 2、重启 mysqld 服务:systemctl restart mysql...

sskill ⋅ 昨天 ⋅ 0

Intellij IDEA神器常用技巧六-Debug详解

在调试代码的时候,你的项目得debug模式启动,也就是点那个绿色的甲虫启动服务器,然后,就可以在代码里面断点调试啦。下面不要在意,这个快捷键具体是啥,因为,这个keymap是可以自己配置的...

Mkeeper ⋅ 昨天 ⋅ 0

zip压缩工具、tar打包、打包并压缩

zip 支持压缩目录 1.在/tmp/目录下创建目录(study_zip)及文件 root@yolks1 study_zip]# !treetree 11└── 2 └── 3 └── test_zip.txt2 directories, 1 file 2.yum...

蛋黄Yolks ⋅ 昨天 ⋅ 0

聊聊HystrixThreadPool

序 本文主要研究一下HystrixThreadPool HystrixThreadPool hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/HystrixThreadPool.java /** * ThreadPool used to executed {@link Hys......

go4it ⋅ 昨天 ⋅ 0

容器之上传镜像到Docker hub

Docker hub在国内可以访问,首先要创建一个账号,这个后面会用到,我是用126邮箱注册的。 1. docker login List-1 Username不能使用你注册的邮箱,要用使用注册时用的username;要输入密码 ...

汉斯-冯-拉特 ⋅ 昨天 ⋅ 0

SpringBoot简单使用ehcache

1,SpringBoot版本 2.0.3.RELEASE ①,pom.xml <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.3.RELE......

暗中观察 ⋅ 昨天 ⋅ 0

Spring源码解析(八)——实例创建(下)

前言 来到实例创建的最后一节,前面已经将一个实例通过不同方式(工厂方法、构造器注入、默认构造器)给创建出来了,下面我们要对创建出来的实例进行一些“加工”处理。 源码解读 回顾下之前...

MarvelCode ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部