• OC的内存管理(一)


    在OC中当一个APP使用的内存超过20M,则系统会向该APP发送 Memory Warning消息,收到此消息后,需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃

    OC内存管理的范围管理范围:  

    1   管理任何继承NSObject的对象,对其他的基本数据类型无效
    2 
    3   本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放与栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露
    4 
    5   new创建在堆区,所以我们主要是对堆区内的内容进行管理

     

    OC内存管理的原理

     1 1)对象的所有权及引用计数
     2     对象所有权概念
     3         任何对象都可能拥有一个或多个所有者.只要一个对象至少还拥有一个所有者,它就会继续存在
     4   Cocoa所有权策略
     5      任何自己创建的对象都归自己所有,可以使用名字以"alloc""new"开头或名字中包含"copy"的方法创建对象,可以使用retain来获得一个对象的所有权
     6 2)对象的引用计数器
     7       每个OC对象都有自己的计数器,是一个整数表示对象被引用的次数,即现在有多少东西在没用这个对象.对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁.
     8 
     9   在每个OC对象内部,都专门有4个字节的存储空间来存储引用计数器.retainCount
    10 3)引用计数器的作用
    11   判断对象要不要回收的唯一依据(存在一种例外:对象值为nil时,引用计数为0,但不回收空间)就是计数器是否为0,若不为0则存在
    12 4)对引用计数器的操作
    13   给对象发送消息,进行相应的计数器操作.
    14    retain消息:使计数器+1,该方法返回对象本身
    15   release消息;使计数器-1(并不代表释放对象)
    16   retainCount消息,获得对象当前的引用计数器  使用%ld / %tu打印
    17 5)对象的销毁
    18   当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收.
    19    当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的"临终遗言"20   一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法).一旦对象被回收了,那么他所占用的存储空间就不再可用,坚持使用会导致崩溃(野指针错误)
    21 
    22 注意:
    23   1)如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就补可能被回收(除非整个程序已经退出)
    24   2)任何一个对象,刚生下来的时候,引用计数器都为1.(对象一旦创建好,默认引用计数器就是1)
    25   3)当使用alloc、new 或者 copy 创建一个对象时,对象的引用计数器默认就是1

    三种内存管理方式:

    1   MannulReference Counting(MRC,手动管理,在开发 ios4.1之前的版本的项目时我们要自己负责使用引用计数来管理内存,比如要手动retain、release、autorelease 等,而在其后的版本可以使用 ARC ,让系统自己管理内存
    2    automatic reference counting(ARC,自动引用计数,ios4.1之后推出的);
    3   garbage collection(垃圾回收).iso不支持垃圾回收;
    4 
    5 其中,ARC作为苹果新提供的计数,苹果推荐开发者使用ARC技术来管理内存;

    手动管理内存快速入门

     1 1.关闭ARC的方法(其中之一) (OC项目创建完成以后默认的是ARC机制,我们要使用MRC就需要关闭ARC)
     2   .设置项目信息
     3   .项目--->bulid setting ->basic  levels --->搜索auto --->ARC的选项改为NO
     4 
     5 2.判断对象是否需要回收
     6     使用引用计数器 > 0 不需要回收 否则回收
     7 3.重写dealloc方法
     8   注意:
     9      dealloc 方法中必须调用[super dealloc];
    10 
    11  4.让引用计数变化
    12    1)使用 release 可以让引用计数-1;
    13    2)使用retain 可以让引用计数器+114  5.内存管理
    15    一般来说,对象的引用计数要进行"平衡"
    16    retain +new = release 次数
    17 例如:
    18   Person *p =[Person new];
    19   NSLog(@"p retainCount =%tu:,[p retainCount]);
    20   [p run];
    21 
    22   [p retain];
    23     //如果想回收,应该让retainCount =0
    24   [p release];
    25   NSLog(@"p retainCount =%tu:,[p retainCount]);
    26   [p release];

    重写dealloc 方法,代码规范

     1 1)一定要[super dealloc] ,而且要放到最后,意义是:先释放子类占用的空间在释放父类占用的空间
     2 2)对self(当前)所拥有的其他对象做一次release操作
     3   -(void)dealloc{
     4     [_car release];
     5     [super dealloc];
     6   }
     7 
     8 注意:
     9   1)永远不要直接通过对象调用dealloc方法(实际上调用并不会出错)
    10     一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)为了防止调用出错,可以将"野指针"指向nil(0)

      

    内存管理的原则

     1 1)原则
     2   只要还有人在使用某个对象,那么这个对象就不会被回收;
     3   只要你想使用这个对象,那么就应该让这个对象的引用计数器 +1;
     4     当你不想使用这个对象时,应该让对象的引用计数器为0;
     5 2)谁创建,谁release
     6   如果你通过alloc new  copy 来创建了一个对象,那么你就必须调用release或者autorelease方法
     7   不是你创建的就不用你去负责
     8 3)谁retain,谁release
     9   只要你调用了retain,无论这个对象是如何生成的,你都要调用release
    10 4)总结
    11   有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1

    内存管理研究内容

    1 1)野指针(僵尸对象)
    2 2)内存泄露

      

    单个对象的野指针问题

     1 例如:
     2   //如果对象被释放了,此时还能不能再继续使用对象
     3   Person *p =[Person new];// retainCount =1
     4   [p run];
     5   //谁创建谁释放
     6   [p release];
     7 
     8   //当 p 被release 之后,p就是一个野指针了
     9   //当p 没有被改变或其他引用时,是可以访问到原来的空间的
    10   //另:内存释放,是将地址的引用释放出来,电脑的删除事实上都是覆盖操作
    11   //那么当p成为野指针后,如何让程序报错,不运行呢?可以开启Xcode中的野指针检测
    12   //在 edit scheme --->Diagnostics--->Objective Enable Zombie Objects 选中就可以了
    13   [p run];// 能运行吗?有可能会运行
    14   //不能使用 [p retain]让僵尸对象起死复生
    15 
    16   //另:空指针:没有指向任何东西的指针,给空指针发送信息不会报错
    17   关于nil 和 Nil 及 NULL的区别
    18 
    19   nil: A null pointer to an Objective-C object.(#define nil((id)0))
    20   nil 是一个对象值
    21   Nil: A null pointer to an Objectve-C class.
    22   NULL : A null pointer to anything else(#define NULL((void*)0))
    23   NULL 是一个通用指针(泛型指针)
    24   NSNull :A class defines a singleton object used to represent null values in collection objects(which don't allow nil values).
    25   [NSNull null]:The singleton instance of NSNull
    26   [NSNull null]是一个对象,它用在不能使用nil的场合

      

    避免使用僵尸对象的方法

    1 为了防止不小心调用了僵尸对象,可以将对象赋值nil(对象的空值)
    2 p=nil;//给p对象赋值 赋值空 ,p =nil后 [p retain],相当于[nil retain],不会报错,但是应避免使用僵尸对象

    对象的内存泄露

     1 1)retain和release个数不匹配,导致内存泄露
     2 
     3   Car *car =[[Car alloc]init];//1
     4   [car retain];//2
     5   [car retain];//3
     6   [car retain];//4
     7   
     8   [car release];//release 让 retainCount-1
     9 
    10   NSLog(@"%ld",car.retainCount);
    11 
    12 2)对象使用过程中被赋值了nil,导致内存泄露
    13   
    14   Car *car =[[Car alloc]init];//1
    15   [car retain];//2
    16   [car retain];//3
    17   [car retain];//4
    18 
    19   car =nil;//car指向了 空
    20   
    21   //释放对象的空间
    22   [car release];  //[nil release];
    23 
    24  3)在方法中不当的使用了retain
    25   -(void)start:(Car *)car;
    26 
    27   -(void)start:(Car *)car{
    28     [car retain];
    29   }

    多个对象的内存管理

    set方法的写法(3)
    1)基本数据类型:直接复制
     -(void) setAge:(int)age{
        _age=age;
      }
    2)OC对象类型
    -(void)setCar:(Car*) car{
      //判断_car存放的是否是 形参对象,如果不是,则执行[_car realease];
      if(_car !=car ){
        [_car release];//先释放上一个对象,(注意第一次是nil发送release消息)
        _car = [car retain];
      }
    }

    循环retain的问题

    ClassA.h:
    
        #import <Foundation/Foundation.h>
        @class ClassB;
        @interface ClassA : NSObject
    
        @property(nonatomic,retain) ClassB *b;
        @end
    
    ClassA.m:
    
        #import "Class A.h"
        #import "ClassB.h"
        @implementation ClassA
    
        -(void)dealloc{
            NSLog(@"a释放了");
            [_b release];
             [super dealloc];
        }
        @end
    
    ClassB.h:
    
        #import <Foundation/Foundation.h>
        @class ClassA;
        @interface ClassB : NSObject
        @property(nonatomic,retain) ClassA *a;
        @end
    
    ClassB.m:
        #import "ClassB.h"
        #import "Class A.h"
        @implementation ClassB
        -(void)dealloc{
                NSLog(@"b释放了");
            [_a release];
            [super dealloc];
        }
        @end
    
    main.m:
        #import <Foundation/Foundation.h>
        #import "Class A.h"
        #import "ClassB.h"
        int main(int argc, const char * argv[]) {
            @autoreleasepool {
                ClassA *classa =[[ClassA alloc]init];
                ClassB *classb =[[ClassB alloc]init];
            
                classa.b=classb;//加上这两段代码,下面的就释放不掉了
                classb.a=classa;
            
                [classb release]; 
                [classa release];
            
            //循环retain的解决方法 1)
            //让其中一个在释放依次
            [classb release]; //在写一次
            //第二种解决方法
            //@property(nonatomic,retain)的时候让一个对象 使用retain 另外一个对象使用assing
            }
            return 0;
        }

    NSString类的内存管理问题

     1 1.多个对象内存泄露问题
     2      1----->NSString *str=[[NSString alloc]initWithString:@"ABC"];
     3      2----->str=@"123";
     4      3----->[str release];
     5      4----->NSLog(@"%@",str);
     6 
     7     首先,咱们先对这段代码进行分析.
     8         第一行: 声明了一个NSString类型的实例 str,并将其初始化init后赋值为@"ABC"
     9     
    10          第二行:将str的指针指向了一个常量@"123"。理论上讲在第一行初始化的@"ABC"没有任何指针指向了.所以造成了内存泄露
    11          第三行:将str的引用计数-1
    12          第四行: 输入str的值 为123
    13          首先回答为什么不会崩溃,因为第三行的release 实际上是release了一个常量@"123",而作为常量其默认的引用计数值是很大的(100K+)
    14         可尝试下输出:
    15             NSLog(@"retainCount = %tu",[@"123" retainCount]);
    16           最终的输出值会是一个很大很大的数。所以单单一个release是不会将其释放掉的.
    17     然后在回答这样会不会造成内存泄露
    18            理论上讲,是会发生的,但是实际上,Objective-C对NSString类型有特殊照顾,所有的NSString的引用计数器默认初始值都会非常大
    19            查阅了一下官方的文档,第一句就是"Do not use this method" 后面给出了说明,因为Autorelease pool的存在,对于内存的管理会相当复杂,retainCount就不能用作调试内存时的依据了,这样对于第一段的结果就可以理解了,可能系统对于这一个特殊的对象有特殊的处理(没准framework里面有早就创建了这个对象了),于是我们拿到了一个非常出人意料的结果

    2.危险地用法

    1 while([a retainCount]>0){
    2       [a release];  
    3 }

    autorelease基本使用

    1 自动释放池
      在ios程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的
      当一个对象调用autorelease时,会将这个对象放到位于栈顶得释放池中


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

        //IOS5.0以前的创建方式
          NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];
          ..........
          [pool release];//[pool drain];用于mac
        //IOSCO.0以后
          @autoreleasepool
          {//开始代表创建自动释放池
            ..........
          }//结束代表销毁自动释放池

    autorelease
      
        是一种支持引用计数的内存管理方式
        它可以暂时的保存某个对象(object),然后在内存池自己排干(drain)的时候对其中的每个对象发送release消息
        注意,这里只是发送release消息,如果当时的引用计数(reference -counted)依然不为0,则该对象依然不会被释放,可以用该方法来保存某个对象,也要注意保存之后要释放该对象

    为什么会有autorelease

     1 OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放
     2 考虑这种情况,如果一个方法需要返回一个新建的对象,该对象如何释放?
     3 方法内部是不会写release来释放对象的,因为这样做会将对象立即释放而返回一个空对象,调用者也不会主动释放该对象的,因为调用者遵循"谁申请,谁释放"的原则.那么这个时候,就发生了内存泄露
     4 
     5 不使用autorelease存在的问题
     6 针对这种情况,Objective-C的设计了autorelease,既能确保对象能正确释放,又能返回有效的对象
     7 
     8 使用autorelease的好处
     9     1)不需要再关心对象释放的时间
    10     2)不需要再关心什么时候调用release

    autorelease基本用法

     1 基本用法:
     2     1)会将对象放到一个自动释放池中
     3     2)当自动释放池被销毁时,会对池子里地所有对象做一次release
     4     3)会返回对象本身
     5     4)调用完autorelease方法后,对象的计数器不受影响(销毁时影响)
     6 
     7 在autorelease的模式下,下述方法是合理地,即可以正确返回结果,也不会造成内存泄露
     8     ClassA *Funcl(){
     9           ClassA *obj = [[[ClassA alloc]init]autorelease];
    10            return obj;
    11     }        

    autorelease是什么原理

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

    autorelease何时释放

    1 对于autorelease pool本身,会在如下两个条件发生时候被释放(详细信息参见第五条)
    2     1)手动释放autorelease pool
    3     2)Runloop结束后自动释放
    4     对于autorelease pool内部的对象
    5     在引用计数的retain ==0 的时候释放,release和autorelease pool的drain都会触发retain --事件
    6     

    autorelease释放的具体原理是什么?

    1 要搞懂具体原理,则先要搞清楚autorelease何时会创建.
    2 我们的程序在main()调用的时候会自动调用一个autorelease,然后在每一个Runloop,系统会隐式创建一个autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶得autorelease pool(main()里的autorelease)会被销毁,这样这个pool里的每一个Object会被release
    3 可以把autorelease pool理解成一个类似父类与子类的关系,main()创建了父类,每个Runloop自动生成的或者开发者自定义的autorelease pool都会成为该父类的子类。当父类被释放的时候,没有被释放的子类也会被释放,这样所有子类中的对象也会收到release消息
    4 那什么是一个Runloop呢?一个UI事件,Timer call,delegate call,一个鼠标事件,键盘按下(MAC OSX),或者iphone上得触摸事件,异步http链接后当接受完数据时,都会是一个新的Runloop。
    5 一般来说,信息循环运行一次是毫秒级甚至微秒级的,因此autorelease的效率仍然是非常高的,确实是一个巧妙地设计

     autorelease注意及错误用法

    1 1.并不是放到自动释放池代码中,都会自动加入到自动释放池
    2     需要 [p autorelease];才会加入到释放池中
    3 2.如果调用autorelease 这个代码没有放到自动释放池中,那也无法调用
    4 3.不管这个对象是在自动释放池内还是外部创建的,只要在自动释放池内写一个 [ p1 autorelease]; pl都会被放到自动释放池中,注意autorelease是一个方法,且只有在自动释放池中使用才有效.

    autorelease的应用场景

    1 经常用来在类方法中快速创建一个对象
    2 person.h 
    3     +(Person * )person;//声明方法
    4 
    5 person.m
    6     -(Person *)person{
    7     
    8           return [[[Person alloc] init] autorelease];  
    9     }    

    完善快速创建对象的方法

    1 如果定义一个学生类Student ,继承自Person
    2 此时还能使用快速创建对象的方法吗?
    3 +(instancetype)person{   //这里要用instancetype 而不是id ,instancetype会自动检测两边类型是否一致,如果使用id 即使 NSString *str=[Student person];编译时也不会报错
    4        return [[[self alloc] init] autorelease];//self指代的是方法的调用者  
    5 }
  • 相关阅读:
    Datastage 调度相关 dsjob
    DropdownList内容树状展示 字段前空格不显示
    IE兼容低版本设置
    跑数速度慢,修改参数
    cognos samples 安装配置【转】
    在子窗口中操作父窗口(刷新)
    html长文本自动换行
    Asp.net禁止缓存
    【Range Lookup】 根据年龄 求年龄分段ID
    目标表已有对应数据则不插入
  • 原文地址:https://www.cnblogs.com/developer-wang/p/4503081.html
Copyright © 2020-2023  润新知