• iOS开发日记45-ARC和内存管理


    今天博主有一个内存管理的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步.

    由于移动设备的内存有限,所以我们需要对内存进行严格的管理,以避免内存泄露造成资源浪费。在OC中,只有对象才属于内存管理范围,例如int、struce等基本数据类型不存在内存管理的概念。在iOS开发中,对内存的管理实际上就是对引用计数器的管理。

    OC内存管理的三种方式

    1. 自动垃圾收集(Automatic Garbage Collection);
    2. 手动引用计数器(Manual Reference Counting)和自动释放池;
    3. 自动引用计数器(Automatic Reference Counting)。

    自动垃圾收集

    在OC2.0中,有一种自动垃圾收集的内存管理形式,通过垃圾自动收集,系统能够自动检测出对象是否拥有其他的对象,当程序运行期间,不被引用的对象就会自动释放。
    说明:在iOS运行环境中不支持自动垃圾收集,在OS X环境才支持,但是Apple现在不建议使用该方法,而是推荐使用ARC进行替代。

    手动引用计数器(MRC)和自动释放池;

    引用计数器的概念

    顾名思义,引用计数器即一个对象被引用(使用)的次数,每个对象的引用计数器占用4个字节
    如下图所示,当使用A创建一个对象object的时候,object的RC默认为1,当B也指向object的时候,object的RC+1=2。然后A指针不指向object的时候,object的RC-1=2-1=1。最后当B也不指向object的时候,object的RC-1=1-1=0,此时对象object被销毁。
    说明: 当一个对象被创建的时候,该对象的RC默认为1;当该对象被引用一次,需要调用retain方法,使RC的值+1;当指针失去对该对象的引用,需要调用release方法,使RC的值-1;当RC=0的时候,该对象被系统自动销毁回收。

    手动引用计数器(MRC)

    MRC即我们通过人为的方式来控制引用计数器的增减,影响对象RC值得方法有以下几种:

    1. new、alloc、copy、mutableCopy,这几种方法用来创建一个新的对象并且获得对象的所有权,此时RC的值默认为RC=1;
    2. retain,对象调用retain方法,该对象的RC+1;
    3. release,对象调用 release方法,该对象的RC-1;
    4. dealloc,dealloc方法并不会影响RC的值,但是当RC的值为0时,系统会调用dealloc方法来销毁对象。
      下面给一个例子:
    //Book类的声明和实现
    @interface Book:NSObject
    
    @end
    @implementation Book
    
    @end
    
    //Peron类的声明和实现
    @interface Person:NSObject
    {
        Book *_book;
    }
    - (void)setBook:(Book *)book;
    @end
    @implementation Person
    - (void)setBook:(Book *)book
    {
        if(_book != book)
        {//如果新设置的book对象不是之前指向的book对象
              [_book release];//使之前对象的RC-1
              _book = [book retain];//当前引用的RC+1  
        }
    - (void)dealloc //重载dealloc方法销毁对象
    {
        [_book release];//由于_book控制了Book对象,_book调用release方法使RC-1
        [super dealloc];//调用父类的dealloc方法,而且必须放在最后一行
    }
    }
    @end
    
    //主函数测试
    void main()
    {
        Book *b=[Book new];
        Person *p = [Person new];
        p.book = b;
        [b release];//b控制了Book对象,b调用release方法使RC-1
        [p release];//p控制了Person对象,p调用release方法使RC-1
    }

    通过上面代码知道,成员变量的设值和取值方法是手动生成的,而且setter方法中成员变量的引用计数器也是手动设置,我们也可以通过@property以及相应关键字来由编译器生成。
    关于在MRC中@property关键字如下:
    1. assign和retain和copy
    这几个关键字用于setter方法的内存管理,如果使用assign(一般用于非OC对象),那么将直接执行赋值操作;如果使用retain(一般用于OC对象),那么将retain新值,release旧值;如果使用copy,那么将release旧值,copy新值。不显示使用assign为默认值
    2. nonatomic和atomic
    这两个关键字用于多线程管理,nonatomic的性能高,atomic的性能低。不显示使用atomic为默认值
    3.readwrite和readonly 
    这两个关键字用于说明是否生成setter方法,readwrite将自动生成setter和getter方法,readonly 只生成getter方法。不显示使用readwrite为默认值
    4. getter和setter
    这两个关键字用于给设值和取值方法另外起一个名字。例如@property(getter=a,setter=b:) int age;相当于取值方法名为a,设值方法名为b:。
    如果使用@property属性,那么上面代码可以改为:

    //Book类的声明和实现
    @interface Book:NSObject
    
    @end
    @implementation Book
    
    @end
    
    //Peron类的声明和实现
    @interface Person:NSObject
    - (void)dealloc;
    @property(nonatomic,retain) Book *_book;
    @end
    @implementation Person
    - (void)dealloc //重载dealloc方法销毁对象
    {
        [_book release];//用于_book控制了Book对象,_book调用release方法使RC-1
        [super dealloc];//调用父类的dealloc方法,而且必须放在最后一行
    }
    }
    @end
    
    //主函数测试
    void main()
    {
        Book *b=[Book new];
        Person *p = [Person new];
        p.book = b;
        [b release];//b控制了Book对象,b调用release方法使RC-1
        [p release];//p控制了Person对象,p调用release方法使RC-1
    }

    循环引用内存管理原则

    对于两个类A包含B,B包含A的循环引用情况下,看如下代码:

    //Book1类的声明和实现
    @interface Book1:NSObject
    @property(nonatomic,retain) Book2 *_book2;
    - (void)dealloc;
    @end
    @implementation Book1
    - (void)dealloc //重载dealloc方法销毁对象
    {
        [_book2 release];//用于_book控制了Book对象,_book调用release方法使RC-1
        [super dealloc];//调用父类的dealloc方法,而且必须放在最后一行
    }
    @end
    
    //Book2类的声明和实现
    @interface Book2:NSObject
    @property(nonatomic,retain) Book1 *_book1;
    - (void)dealloc;
    @end
    @implementation Book2
    - (void)dealloc //重载dealloc方法销毁对象
    {
        [_book1 release];//用于_book控制了Book对象,_book调用release方法使RC-1
        [super dealloc];//调用父类的dealloc方法,而且必须放在最后一行
    }
    }
    @end
    
    //主函数测试
    void main()
    {
        Book1 *b1=[[Book1 alloc] init];
        Book2 *b2=[[Book2 alloc] init];
        b1.book2 = b2;
        b2.book1 = b1;
        [b1 release];
        [b2 release];
    }

    下面分析主函数代码,当执行Book1 *b1=[[Book1 alloc] init]后,b1指向Book1。当执行Book2 *b2=[[Book2 alloc] init]后,b2指向Book2。 当执行b1.book2 = b2后,Book1的成员变量_b2指向Book2。当执行b2.book1 = b1后,Book2的成员变量_b1指向Book1。内存中具体关系如下图所示。

    此时Book1的引用计数器RC=2,Book2的引用计数器RC=2。
    当执行 [b1 release]后,b1释放对Book1的控制权,此时Book1的引用计数器RC=2-1=1。
    当执行[b2 release]后,b2释放对Book2的控制权,此时Book2的引用计数器RC=2-1=1。
    那么由于仍有指针指向Book1和Book2,这时内存中Book1和Book2的关系如黑色椭圆内所示。所以并不会调用dealloc函数,所以Book1和Book2并不会毁销,这样就造成了 内存泄露


    对于上面这种情况,只需要在Book1和Book2的@property属性声明中一端使用retain,一端使用assign。即将@property(nonatomic,retain) Book1 *_book1或者@property(nonatomic,retain) Book2 *_book2中的一个retian改为assign。具体原因自己分析。

    看下面这种循环引用情况,只能使用assign

    @interface Book:NSObject
    @property(nonatomic,assign)id  instance;  //此处必须用assign
    - (void)dealloc;
    @end
    @implementation Book
    - (void)dealloc //重载dealloc方法销毁对象
    {
        [_instance release];//用于_book控制了Book对象,_book调用release方法使RC-1
        [super dealloc];//调用父类的dealloc方法,而且必须放在最后一行
    }
    @end
    
    void mian()
    {
        Book b1 = [[Book alloc] init];
        Book b2 = [[Book alloc] init];
        b1.instance = b2;
        b2.instance = b1;
        [b1 release];
        [b2 release];
    }

    大家可以分析一下,如果@property(nonatomic,assign)id instance; 中将assign换为retain,那么也将造成内存泄露。

    Autorelease Pool的使用

    顾名思义,autorelease即自动释放对象,不需要我们手动释放。从上面代码我们知道,在主函数中,创建对象obj后,总要手动调用[obj release]方法,这样无疑使工作量变大,且对我们的技术增长毫无意义。为了减少这种无意义的工作,可以使用Autorelease Pool方式。
    Autorelease Pool即自动释放池,在Autorelease Pool内的对象在创建时只要调用了autorelease方法,那么在该池子内的对象的最后的release方法的调用将由编译器完成。

    Autorelease Pool的创建有两种方式:

    1.通过@autoreleasepool方法创建,如下:
    @autoreleasepool{
    //在大括号内创建的对象最后不需要手动调用release方法。
    }
    2. 通过NSAutoreleasePool类创建,如下:
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]  init];
    //此范围为自动释放池
    [pool release];

    Autorelease Pool的使用例子如下:

    void main()
    { 
        @autoreleasepool
        {
            Person p = [[[Person alloc] init] autorelease]; //调用autorelease方法
            //不需要再调用[p release];方法,超过autoreleasepool作用域会自动调用该方法。
        }    
    }

    在返回对象的方法中最好使用自动释放池释放对象,因为如果将新创建的对象作为返回值,由于在返回该对象之前并不能释放该对象,所以可以通过自动释放池来延迟该对象的释放。示例代码如下:

    -(Person *)getNewPerson
    {
        Person p* = [[[Person alloc] init] autorelease];
        //do something
        return p;
    }
    或者
    {
        Person p* = [[Person alloc] init] ;
        //do something
        return [p autorelease];
    }

    注意点:

    1. release方法不能多次调用,该调用的时候调用,否则容易造成野指针错误。
    2. 创建对象时多次调用autorelease方法,容易造成野指针错误。
    3. 在自动释放池中新创建的对象并不是一定会添加到释放池中,例如由new、alloc、copy、mutableCopy创建的对象并不会加到自动释放池中,并且必须手动调用release方法才能释放对象。如果想让新创建的对象加入到自动释放池中,就必须调用autorelease方法。
    4. 使用autorelease方法并不会使引用计数器的值增加,只是表示将该对象加入到自动释放池中。

    自动引用计数器(ARC)

    ARC将由编译器来自动完成对象引用计数器的控制,不需要手动完成。
    ARC模式下,创建的新对象通常由以下几种关键字来限定。

    1. __strong(默认值),由__strong修饰的为强指针,对象只要有强指针指向就不会被销毁;每当一个强指针指向一个对象,该对象的的RC+1;
    2. __weak,由__weak修饰的为弱指针,弱指针所指向的对象并不会改变RC值,弱指针只表示是对对象的引用;当弱指针所指向的对象销毁时,该弱指针的值变为nil;
    3. __unsafe_unretained,__unsafe_unretained修饰的对象指针所指向的对象也不会改变RC值,也只表示是对对象的引用;当所指向的对象销毁时,该指针的值不会变为nil,仍是保留原有的地址;

    在ARC模式下,MRC中的retain、release等方法变的不可用,因为ARC是不需要我们手动管理内存的,一切由编译器完成。
    MRC模式下,将一个对象指针赋值给另一个对象指针如下:

    Person p1 = [Person new];
    Person p2 = [Person new];
    [p2 release]//在p2失去对对象的控制权时需要先release
    p2 = p1;//进行赋值操作

    但是在ARC模式下,我们完全可以不关心具体怎么操作,只需要直接进行赋值即可:

    Person p1 = [Person new];
    Person p2 = [Person new];
    p2 = p1;//进行赋值操作

    ARC模式下的循环引用

    在ARC模式下,@property属性关于内存管理的修饰符为strong和weak(MRC下的retain和assign不可用),表示声明为强指针还是弱指针。通常情况下都是使用strong来修饰,但是在循环引用却不是。

    下面这种情况一端使用strong修饰,一端使用weak修饰。如果都使用strong修饰,那么将造成对象的循环保持,造成内存泄露。

    //Book1类的声明和实现
    @interface Book1:NSObject
    @property(nonatomic,strong) Book2 *_book2;
    @end
    @implementation Book1
    
    @end
    
    //Book2类的声明和实现
    @interface Book2:NSObject
    @property(nonatomic,weak) Book1 *_book1;
    @end
    @implementation Book2
    
    @end
    
    //主函数测试
    void main()
    {
        Book1 *b1=[[Book1 alloc] init];
        Book2 *b2=[[Book2 alloc] init];
        b1.book2 = b2;
        b2.book1 = b1;
    }

    下面这种循环引用情况,只能使用weak。如果使用strong修饰,那么将造成对象的循环保持,造成内存泄露。

    @interface Book:NSObject
    @property(nonatomic,weak)id  instance;  //此处必须用assign
    @end
    @implementation Book
    
    @end
    
    void mian()
    {
        Book b1 = [[Book alloc] init];
        Book b2 = [[Book alloc] init];
        b1.instance = b2;
        b2.instance = b1;
    }

    注意点:

    1. ARC模式下仍能使用自动释放池;
    2. MRC下的retain、release、retainCount、autorelease等方法不可使用。
    3. 注意循环引用下strong和weak的选择。

    总结

    内存管理的本质是对对象引用计数器的操作,理解MRC模式下内存管理操作有助于我们对OC内存管理的理解。内存管理只针对对象而言,注意MRC和ARC下@property属性关键字的选择,在MRC模式下,OC对象通常使用retain关键字,非OC对象使用assign关键字,但是循环引用是一个例外,通常需要一端使用assign,一端使用retain;在ARC模式下,OC对象通常使用strong关键字,非OC对象使用assign关键字,但是循环引用是一个例外,通常需要一端使用strong,一端使用weak

    http://www.jianshu.com/p/89f6e9f4e37f?plg_nld=1&plg_uin=1&plg_auth=1&plg_nld=1&plg_usr=1&plg_vkey=1&plg_dev=1

    http://www.tuicool.com/articles/fqEbMz6?plg_nld=1&plg_uin=1&plg_auth=1&plg_nld=1&plg_usr=1&plg_vkey=1&plg_dev=1

    http://www.tuicool.com/articles/IR7v6vJ?plg_nld=1&plg_uin=1&plg_auth=1&plg_nld=1&plg_usr=1&plg_vkey=1&plg_dev=1

  • 相关阅读:
    JSP实现页面自动跳转
    marquee属性的使用说明
    jsp 按钮 超链接 直接跳转至另一页面
    生成验证码图片代码
    js 通过判断月数填充日数下拉框
    邮箱正则验证
    jsp 验证用正则表达式
    onselectstart 与 -moz-user-select
    onselectstart 、onselect区别
    NSOperation基本操作
  • 原文地址:https://www.cnblogs.com/Twisted-Fate/p/4950315.html
Copyright © 2020-2023  润新知