美文网首页
内存管理-MRC

内存管理-MRC

作者: 紫荆秋雪_文 | 来源:发表于2018-07-27 16:56 被阅读18次

    现在使用OC编写iOS程序不需要考虑创建的对象该什么时候释放,这是因为引入了自动管理内存机制ARC,在自动管理内存机制MRC时期,对象的释放时需要程序员自己来控制的。下面看看在MRC内存管理机制下是怎么管理对象的释放的,因为ARC也是对MRC的管理,这样清楚了MRC下的内存管理机制后,也就了解了ARC下的内存管理机制

    OC对象的内存管理

    • 在iOS中,使用引用计数来管理OC对象的内存
    • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放占用的内存空间
    • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1

    MRC环境 内存管理MRC环境.png

    自定义对象的内存管理

    • 自定义RevanPerson类
    #import <Foundation/Foundation.h>
    
    @interface RevanPerson : NSObject
    
    @end
    
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [super dealloc];
    }
    
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];
        //释放对象
        [person release];
    }
    
    @end
    
    • 打印输出
    2018-07-27 15:32:12.167713+0800 03-RevanPerson[46646:2945558] -[RevanPerson dealloc]
    
    • 小结:当不用的对象需要手动调用release,引用计数为0时对象释放

    对象拥有一个对象的属性

    情景:person对象拥有一辆车

    • RevanCar
    #import <Foundation/Foundation.h>
    
    @interface RevanCar : NSObject
    - (void)run;
    @end
    
    
    #import "RevanCar.h"
    
    @implementation RevanCar
    
    - (void)run {
        NSLog(@"%s", __func__);
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [super dealloc];
    }
    
    @end
    
    • RevanPerson
    #import <Foundation/Foundation.h>
    #import "RevanCar.h"
    
    @interface RevanPerson : NSObject {
        RevanCar *_car;
    }
    
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car;
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar;
    
    @end
    
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car {
        _car = car;
    }
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar {
        return _car;
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [super dealloc];
    }
    
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        [person setCar:car1];
        
        [[person getCar] run];
        
        [car1 run];
        //释放对象car1
        [car1 release];//car1 :0
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 打印输出
    2018-07-27 15:45:50.461635+0800 03-RevanPerson[46866:2956657] -[RevanCar run]
    2018-07-27 15:45:50.462310+0800 03-RevanPerson[46866:2956657] -[RevanCar dealloc]
    2018-07-27 15:45:50.463140+0800 03-RevanPerson[46866:2956657] -[RevanPerson dealloc]
    
    • 分析:
      • car1对象和person对象在使用完以后内存都可以释放
    • 根据内存管理的规则,在对象释放之前都可以使用该对象

    car1对象释放后调用[[person getCar] run]

    • 测试代码
    //
    //  ViewController.m
    //  03-RevanPerson
    //
    //  Created by 紫荆秋雪 on 2018/7/27.
    //  Copyright © 2018年 紫荆秋雪. All rights reserved.
    //
    
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        [person setCar:car1];
        
        [[person getCar] run];
        
        //释放对象car1
        [car1 release];//car1 :0
        
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 程序崩溃(Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT))
    • 分析:
      • [car1 release]之后car1对象的内存空间就已经释放了,但是person对象中的car属性依然指向被释放的内存空间地址,当再执行run方法时就会崩溃,这是野指针操作。
      • 但是在真实的情况下,无法控制person什么时候调用car中的run方法,所以在car赋值给person时,person对car也进行一次引用,这样无论car1对象什么时候调用release方法,car1的对象都不会释放

    setCar方法中对car对象进行一次retain操作

    • RevanPerson
    #import <Foundation/Foundation.h>
    #import "RevanCar.h"
    
    @interface RevanPerson : NSObject {
        RevanCar *_car;
    }
    
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car;
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar;
    
    @end
    
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car {
        _car = [car retain];
    }
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar {
        return _car;
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [super dealloc];
    }
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        [person setCar:car1];//car1 :2
        
        [[person getCar] run];
        
        //释放对象car1
        [car1 release];//car1 :1
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 打印输出
    2018-07-27 16:09:02.325394+0800 03-RevanPerson[47207:2973778] -[RevanCar run]
    2018-07-27 16:09:02.325611+0800 03-RevanPerson[47207:2973778] -[RevanCar run]
    2018-07-27 16:09:02.326467+0800 03-RevanPerson[47207:2973778] -[RevanPerson dealloc]
    
    • 问题:
      • 虽然解决了崩溃的问题
      • 发现car1对象内存泄露了
    • 分析
      • 虽然在set方法中对car对象进行一次retain操作,让car对象的引用计数+1,解决了程序的崩溃问题,但是造成了car对象的内存泄露。虽然可以在[[person getCar] run];后面紧接着调用[[person getCar] release];但是这样依然存在person对象再次调用car的run方法时程序崩溃的风险,但是如果在person对象释放的时候同时释放car对象这个问题就可以完美解决了
    • 修改代码
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car {
        _car = [car retain];
    }
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar {
        return _car;
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [_car release];
        [super dealloc];
    }
    
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        [person setCar:car1];//car1 :2
        
        [[person getCar] run];
        
        //释放对象car1
        [car1 release];//car1 :1
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 打印输出
    2018-07-27 16:19:26.164249+0800 03-RevanPerson[47355:2982538] -[RevanCar run]
    2018-07-27 16:19:26.164438+0800 03-RevanPerson[47355:2982538] -[RevanCar run]
    2018-07-27 16:19:26.164548+0800 03-RevanPerson[47355:2982538] -[RevanPerson dealloc]
    2018-07-27 16:19:26.164885+0800 03-RevanPerson[47355:2982538] -[RevanCar dealloc]
    

    person对象属性对象连续被不同对象赋值时

    情景:当person对象中的car属性连续被不同的car对象赋值会怎么样

    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        RevanCar *car2 = [[RevanCar alloc] init];//car2 :1
        
        [person setCar:car1];//car1 :2
        [person setCar:car2];//car2 :2
        
        [[person getCar] run];
        
        //释放对象car1
        [car1 release];//car1 :1
        [car2 release];//car1 :1
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 打印输出
    2018-07-27 16:22:58.699056+0800 03-RevanPerson[47442:2985678] -[RevanCar run]
    2018-07-27 16:22:58.699304+0800 03-RevanPerson[47442:2985678] -[RevanCar run]
    2018-07-27 16:22:58.699423+0800 03-RevanPerson[47442:2985678] -[RevanPerson dealloc]
    2018-07-27 16:22:58.699564+0800 03-RevanPerson[47442:2985678] -[RevanCar dealloc]
    
    • 问题
      • 在测试代码中有person、car1、car2共3个对象,但是从打印输出来看person对象释放了,但是2个car对象只释放了一个,所以有一个car对象造成了内存泄露
      • 在person对象释放的时候同时会调用属性car的release也就是说会释放最后赋值给person对象中属性car被释放了,但是开始赋值的car1对象内存没有释放

    为了解决这个内存泄露的问题,可以在set方法中先对_car进行一次release操作,也就是先把就属性对象释放然后再赋值

    • 修改代码
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car {
        [_car release];
        _car = [car retain];
    }
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar {
        return _car;
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [_car release];
        [super dealloc];
    }
    
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        RevanCar *car2 = [[RevanCar alloc] init];//car2 :1
        
        [person setCar:car1];//car1 :2
        [person setCar:car2];//car2 :2
        
        [[person getCar] run];
        
        //释放对象car1
        [car1 release];//car1 :1
        [car2 release];//car1 :1
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 打印输出
    2018-07-27 16:33:02.448202+0800 03-RevanPerson[47549:2992057] -[RevanCar run]
    2018-07-27 16:33:02.448379+0800 03-RevanPerson[47549:2992057] -[RevanCar dealloc]
    2018-07-27 16:33:02.448498+0800 03-RevanPerson[47549:2992057] -[RevanCar run]
    2018-07-27 16:33:02.448621+0800 03-RevanPerson[47549:2992057] -[RevanPerson dealloc]
    2018-07-27 16:33:02.448725+0800 03-RevanPerson[47549:2992057] -[RevanCar dealloc]
    
    • person对象和2个RevanCar对象销毁了

    僵尸对象

    情景:在[car1 release]方法之后再重新把car1赋值给person对象

    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        [person setCar:car1];//car1 :2
        
        //释放对象car1
        [car1 release];//car1 :1
        
        //car1多次赋值给person
        [person setCar:car1];
        [person setCar:car1];
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 程序崩溃 使用僵尸对象.png
    • 分析
      • 当初始将car1赋值给person对象时,person对象中的_car=car1,此时的car1对象的引用计数为2;当调用[car1 release]之后,car1对象的引用计数为1;当再次把car1对象赋值给person对象时,进入setCar方法后首先会执行[_car release],执行完以后car1对象的引用计数为0,car1对象释放,但是此时的car就是第二次传入的car1对象,当执行[car retain]时,car(car1)对象已经释放了,使用一个已经释放的对象造成了使用僵尸对象崩溃
      • 可以判断一下传入的参数是否和现在拥有的参数是同一个,如果是就不需要在重新赋值了

    决定僵尸对象崩溃

    • 修改RevanPerson
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    /**
     给属性_car赋值
     */
    - (void)setCar:(RevanCar *)car {
        if (_car != car) {
            [_car release];
            _car = [car retain];
        }
    }
    
    /**
     获取person对象中的_car属性
     */
    - (RevanCar *)getCar {
        return _car;
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [_car release];
        _car = nil;
        [super dealloc];
    }
    
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //创建对象
        RevanPerson *person = [[RevanPerson alloc] init];//person :1
        //创建一个car
        RevanCar *car1 = [[RevanCar alloc] init];//car1 :1
        
        [person setCar:car1];//car1 :2
        
        //释放对象car1
        [car1 release];//car1 :1
        
        //car1多次赋值给person
        [person setCar:car1];
        [person setCar:car1];
        
        [[person getCar] run];
        //释放对象person
        [person release];//person :0
    }
    
    @end
    
    • 打印输出
    2018-07-27 16:52:04.079668+0800 03-RevanPerson[47837:3006816] -[RevanCar run]
    2018-07-27 16:52:04.079846+0800 03-RevanPerson[47837:3006816] -[RevanPerson dealloc]
    2018-07-27 16:52:04.079950+0800 03-RevanPerson[47837:3006816] -[RevanCar dealloc]
    

    小结

    • 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,需要调用release或者autorelease来释放它

    相关文章

      网友评论

          本文标题:内存管理-MRC

          本文链接:https://www.haomeiwen.com/subject/bthgmftx.html