• [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

  • 相关阅读:
    远程仓库
    本地仓库
    仓库
    坐标和依赖
    my24_mysql索引-使用篇
    my23_pxc其中一个节点重建记录
    1.1 Rust安装
    1.2 学习笔记之数据类型
    my22_mydumper 使用总结
    my21_myloader -o参数
  • 原文地址:https://www.cnblogs.com/lqios/p/4288271.html
Copyright © 2020-2023  润新知