zoukankan      html  css  js  c++  java
  • [objective-c] 08

     

    OC语言中的内存管理机制为ARC(Automatic Reference Counting,自动引用计数)。于2011年中旬推出,替换陈旧且低效的手动内存管理,关于手动内存管理的内容,本章教程不再讲授。本章主要从以下几个方面对内存管理进行展开讲解。

    • 内存管理原则
    • 对象引用类型
    • 属性引用类型
    • 强引用循环
    • AUTO类型与释放池

    1.内存管理原则

    核心原则:没有被对象指针使用(指向)的内存立即释放。这个原则决定一般情况下,不会有内存泄露的情况存在,但存在特殊情况,也是本章最后一个专题要阐述的问题。

    内存泄露是指,一块没有被指针引用的内存,但由于一些原因,无法释放,造成内存浪费的情况。

    管理原则

    • 强引用对象指针使用中的内存绝对不会释放。
    • 归零弱引用对象指针使用中的内存,释放情况由其他对象指针决定,本身不影响内存释放与否,但其指向的内存一旦释放,本身立即置nil,保证不出现野指针。
    • 弱引用对象指针使用中的内存,释放情况由其他对象指针决定,本身不影响内存释放与否,但其指向的内存一旦释放,本身值不会发生改变,会出现野指针的情况。
    • AUTO类型对象指针使用过或使用中的内存,出释放池才会释放。通过AUTO类型与释放池配合使用,可以精确调节内存时间,提前或延后。

    2.对象引用类型

    对象引用类型有如下四种

    • 强引用:__strong修饰的对象指针或无修饰的对象指针
    • 归零弱引用:__weak修饰的对象指针
    • 弱引用:__unsafe__unretain修饰的对象指针
    • AUTO类型:__autoreleasing修饰的对象指针

    首先分析强引用对象指针的使用情况。在分析内存释放情况时,我们需要一个测试类进行释放测试。当一个对象释放时,它的dealloc方法会被调用。所以我们在dealloc方法中进行相关输出,便能精确看到,该对象何时释放。

    @interface Test : NSObject
    
    @end
    
    @implementation Test
    
    - (void)dealloc
    {
        NSLog(@"该对象释放");
    }
    
    @end
    
    

    情况1

    int main(int argc, const char * argv[])
    {
        {
            Test * t = [[Test alloc]init];
        }
        //代码运行至此,t的作用域结束,t指向的内存并无其他对象指针使用,所以,该内存在此释放。
    
        return 0;
    }
    

    情况2

    int main(int argc, const char * argv[])
    {
        {
            Test * t1;
            {
                Test * t = [[Test alloc]init];
                t1 = t;
            }
            //代码运行至此,t作用域结束,但t指向的内存仍有t1对象指针使用,所以在此该内存不会释放。     
        }
        //代码运行至此,t1作用域结束,t1指向的内存再无其他对象指针使用,所以在此内存释放。
    
        return 0;
    }
    

    情况3

    int main(int argc, const char * argv[])
    {
        {
            Test * t = [[Test alloc]init];
            t = nil;//代码运行至此,t不再指向之前分配的内存,之前的内存无对象指针使用,释放。
        }   
        return 0;
    }
    

    以上是强引用的常用情况,其对象指针并无任何修饰符进行修饰,但已经OC语法规定,对象指针无修饰时,为强引用类型。

    我们继续讨论归零弱引用类型的对象指针对内存的影响。

    情况1

    int main(int argc, const char * argv[])
    {
        {
            __weak Test * t1;
            {
                Test * t = [[Test alloc]init];
                t1 = t;
            }
            //代码运行至此,t作用域结束,t指向的内存仍有t1对象指针使用,但t1为归零弱引用,不会影响对象释放情况,所以在此内存释放,且t1本身值变为nil    
        }
    
    
        return 0;
    }
    

    情况2

    int main(int argc, const char * argv[])
    {
    
        __weak Test * t1 = [[Test alloc]init];//在此语句运行时,分配的内存并无除弱引用对象指针以外的对象指针使用,所以,该内存立即释放。
    
        return 0;
    }
    
    

    以上是归零弱引用的情况,不常用。归零弱引用只有一种情况下使用:

    • 代码块回调接口中的自身代码块引用自身对象

    例如:

    @interface Room : NSObject
    @property (strong, nonatomic) Light *lightA;
    @property (strong, nonatomic) SwitchB *s;
    
    @end
    
    
    @implementation Room
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.lightA = [[Light alloc] init];
            self.s = [[SwitchB alloc] init];
    
            __weak __block Room * copy_self = self;//打破强引用循环,强引用循环的概念下文会讲解。
    
            self.s.changeStateBlockHandle = ^(SwitchState state)
            {
                if (state == SwitchStateOff)
                {
                    [self.lightA turnOff];
                }
                else
                {
                    [self.lightA turnOn];
                }
            };
        }
        return self;
    }
    @end
    

    弱引用和归零弱引用管理内存的释放时间相同。弱引用是OC为兼容之前特殊情况下的内存管理而做的一个不常用类型。所以之后,我们不会使用弱引用,所有需要弱引用的地方全部以归零弱引用代替。

    3.属性引用类型

    属性的内存控制符,有四种情况。

    • strong
    • weak
    • copy
    • assgin

    对于对象来说,可选的只有前三种。第四种,assgin为无内存管理,主要针对基础数据类型设计。例如

    @property (assgin, nonatomic) int a;
    

    如果一个属性是对象,那么其属性内存控制符必然是前三种之一。

    strong:默认情况下使用,除特殊情况。

    weak:在遇到强引用循环时,使用。

    copy:在进行数值对象赋值时,使用。

    例如

    @property (copy, nonatomic) NSString * name;
    @property (copy, nonatomic) NSNumber * age;
    

    但也可以strong代替,所以,copy使用场景也不多见。

    4.强引用循环

    在内存管理中,强引用循环是最严重的失误,会造成大量内存泄露。通过一个例子来说明为什么会产生泄露。

    首先用实际生活中的一个场景来具体的说明一下,问题产生的原因。

    例如,现在在一个教室,有学生有老师。老师对自己的要求是,学生不离开教室,我就不离开教室。而学生对自己的要求是,老师不离开教室,我就不离开教室。这样一直持续下去的结果就是,双方谁都不会离开教室。

    然后我们再看一下代码中何时会产生这种情况。

    @interface Student : NSObject
    
    @property (strong, nonatomic) Teacher *tea;
    @end
    
    @interface Teacher : NSObject
    
    @property (strong, nonatomic) Student *stu;
    @end
    
    main()
    {
        {
            Teacher * tea = [[Teacher alloc] init];
            Student * stu = [[Student alloc] init];
    
            tea.stu = stu;
            stu.tea = tea;
        }
    
    }
    

    上述代码中,可以发现,Student类有一个strong类型的属性tea,通过管理原则我们可以知道,stu对象存在其强引用属性tea一定存在,不会释放。同样Teacher有一个strong属性stu,tea对象存在意味着stu对象也绝对不会释放。这样当两个对象指针作用域消失时,其使用的内存无法释放,出现内存泄露。

    这种问题便是内存管理中会遇到的强引用循环,也是目前能够造成内存泄露的唯一原因。需要对这样的情况在设计层面进行避免。互相包含对方类型的属性的结构中,必须有一方为归零弱引用。

    目前存在双向包含的场景只有在回调中会用到

    • 目标动作回调中,储存target对象的属性为weak
    • 委托回调中,储存delegate委托人的属性为weak

    除上述两种情况外,其他地方默认使用strong修饰属性即可。

    5.AUTO类型与释放池

    在内存管理中有一种较为特殊的类型叫AUTO类型,虽然名字和自动相关,但其释放仍需要手动配置释放池来调整。

    __autoreleasing:被此种修饰符修饰的对象指针,其使用过和使用中的内存在出释放池时才会释放。所以可以通过手动配置自动释放池的位置来调节释放时间。

    延迟释放的例子:

    @autoreleasepool 
    {
       {
            __autoreleasing Student * stu = [[Student alloc] init];
       }//在此,stu的作用域虽然已经结束,但stu为AUTO类型,所以等代码运行到释放池结束才会释放
    }
    //在此位置,内存释放
    
    

    提前释放的例子:

    __autoreleasing Student * stu;
    @autoreleasepool 
    {
       stu = [[Student alloc] init];
    }
    //在此位置,内存释放,虽然stu的作用域没有结束
    
    

    使用AUTO的类型有两种情况

    情况1为对象的便利构造器方法,需要延迟释放

    +(id)student
    {
        __autoreleasing Student * stu = [[Student alloc] init];
        return stu;
    }
    

    OC语言规定,方法返回的内存必须为AUTO类型。

    情况2为在一个封闭循环内,用便利构造器创建对象

    for(int i = 0;i<10000;i++)
    {
        @autoreleasepool 
        {
            __autoreleasing Student * stu = [Student student];
        }
    }
    

    因便利构造器返回的对象为AUTO类型,所以该对象指针使用的内存只有在出释放池时才会释放。但for循环中无释放池,这会造成,大量无用的对象无法立即释放。

    添加释放池之后,内存便可以在使用结束之后立即释放。

     

    [代码展示]

     

    1.

    ======Test类的声明======

    #import <Foundation/Foundation.h>

    @interface Test : NSObject

    @end

     ======Test类的实现======

    #import "Test.h"

    @implementation Test

    //对象被释放之前要调用的方法

    -(void)dealloc

    {

        NSLog(@"Test被释放。");

    }

    @end

     ======main======

    #import <Foundation/Foundation.h>

    #import "Test.h"

    int main(int argc, const char * argv[]) {

        //__strong 强引用,它会造成对象的引用计数器的变化(+1)

        @autoreleasepool {

            

           __strong Test *t2;

            {

                Test *t = [[Test alloc]init];

                t2 = t;

                t=nil;

                NSLog(@"2");

            }

            NSLog(@"1");

        }

        NSLog(@"3");

        

       

        return 0;

    }

    ======运行结果======

    2

    1

    Test被释放。

    3

    2.

    ======Test类的声明======

    #import <Foundation/Foundation.h>

    @interface Test : NSObject

    @end

    ======Test类的实现======

    #import "Test.h"

    @implementation Test

    -(void)dealloc

    {

        NSLog(@"Test被释放");

    }

    @end

    ======main======

    #import <Foundation/Foundation.h>

    #import "Test.h"

    int main(int argc, const char * argv[]) {

    //    @autoreleasepool {

    //        //__weak 弱引用,不会造成引用计数器的变化,同时也不能阻止对象的释放,对象被释放后自动指向了nil

    //        __weak Test *t2;

    //        {

    //            __strong Test *t = [[Test alloc]init];

    //            t2 = t;

    //            NSLog(@"%p",t2);

    //            NSLog(@"1");

    //        }

    //        NSLog(@"%p",t2);

    //        NSLog(@"2");

    //    }

        

        @autoreleasepool {

            //__unsafe_unretained 与__weak相同,在对象被释放后不会指向nil

            __unsafe_unretained Test *t2;

            {

                __strong Test *t = [[Test alloc]init];

                t2 = t;

                NSLog(@"%p",t2);

                NSLog(@"1");

            }

            NSLog(@"%p",t2);

            NSLog(@"2");

        }

        return 0;

    }

    ======运行结果======

    0x100300220

    1

    Test被释放

    0x100300220

    2

  • 相关阅读:
    298. Binary Tree Longest Consecutive Sequence
    117. Populating Next Right Pointers in Each Node II
    116. Populating Next Right Pointers in Each Node
    163. Missing Ranges
    336. Palindrome Pairs
    727. Minimum Window Subsequence
    211. Add and Search Word
    年底购物狂欢,移动支付安全不容忽视
    成为程序员前需要做的10件事
    全球首推iOS应用防破解技术!
  • 原文地址:https://www.cnblogs.com/lqios/p/4288271.html
Copyright © 2011-2022 走看看