内存管理-MRC

现在使用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来释放它
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容