OC基础 05-手动内存管理(MRC)

来源:互联网 时间:1970-01-01

---恢复内容开始---

MRC: Manul(手动) Reference(引用) Counting(计数)

什么是手动引用计数?

所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码

内存管理的原则就是有加就有减

也就是说, 一次alloc/new/copy对应一次release, 一次retain对应一次relese

内存管理的重要性:

移动设备的内存极其有限,每个app的占用内存也是有限制的.

当我们创建一个OC对象,定义一个变量,调用一个函数或者方法,都会增加app的内存占用.

当app内存占用极多的时候,系统会发出内存警告,而这时候就需要回收一些不需要在使用的内存空间,比如"对象""变量";

如果app占用内存极大的时候,会造成程序崩溃,闪退等现象,极大的影响了用户体验

什么是内存管理?

>对内存进行管理

内存管理涉及的操作?

>分配内存:创建对象.增加内存占用

>清楚内存:释放对象,减少内存占用

OC中内存管理的范围?

>任何继承NSObject的对象

>对非对象类型无效

为什么只有OC对象才需要内存管理,本质是什么?

>OC对象存放于堆中

>非OC对象一般存放于栈中(存放于栈中的由系统自动回收)

int main(int argc, const char * argv[]){@autoreleasepool {int a = 10; // 栈int b = 20; // 栈// p : 栈// Person对象(计数器==1) : 堆Person *p = [[Person alloc] init];}// 经过上一行代码后, 栈里面的变量a/b/c都会被回收// 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1return 0;}

引用计数器

>每个OC对象都有一个计数器

>它是一个整数

>可以理解为"对象被引用的次数"或者"有多少人再用它"

引用计数器的作用?

>系统根据对象的引用计数器判断对象是否需要被回收

>当对象的引用计数器为0时,对象所占用的内存空间就会被系统回收

>如果对象的引用计数器不为0时,那么在整个程序运行期间,对象所占用的内存空间就不会被释放,除非整个程序已经退出.

任何一个对象刚创建出来,期引用计数器为1

>当使用alloc new copy创建对象时,对象的引用计数器默认就为1

引用计数器的常见操作

>给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)

>给对象发送一条release消息, 可以使引用计数器值-1

>给对象发送retainCount消息, 可以获得当前的引用计数器值

注意: release并不代表销毁/回收对象, 仅仅是计数器-1

dealloc方法

>当一个对象的引用计数器值为0时,这个对象即将被销毁,其占用的内存被系统回收

>对象即将被销毁时系统会自动给对象发送一条dealloc消息 (因此, 从dealloc方法有没有被调用,就可以判断出对象是否被销毁)

dealloc方法的重写

>一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言

>一旦重写了dealloc方法, 就必须调用[super dealloc],并且放在最后面调用

注意

>不能直接调用dealloc方法

>一旦对象被回收了, 它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)

野指针/空指针/僵尸对象

野指针

>指向不可用内存(僵尸对象)的的指针,我们称之为野指针

>给野指针发消息会报EXC_BAD_ACCESS错误

空指针

>没有指向存储空间的指针(里面存的是nil, 也就是0)

>给空指针发消息是没有任何反应的

>避免野指针错误的常见办法(在对象被销毁之后, 将指向对象的指针变为空指针)

僵尸对象

>已经被释放的对象,不能在被使用的对象

Xcode打开僵尸对象检控

内存管理原则

苹果官方规定的内存管理原则

谁创建谁release :

如果你通过alloc、new、copy或mutableCopy来创建一个对象,那么你必须调用release或autorelease

谁retain谁release:

只要你调用了retain,就必须调用一次release

总结:

有加就有减 曾经让对象的计数器+1,就必须在最后让对象计数器-1 多对象内存管理

单个对象的内存管理, 看起来非常简单

如果对多个对象进行内存管理, 并且对象之间是有联系的, 那么管理就会变得比较复杂

总的来说, 有这么几点管理规律

只要还有人在用某个对象,那么这个对象就不会被回收 只要你想用这个对象,就让对象的计数器+1 当你不再使用这个对象时,就让对象的计数器-1 set方法内存管理 (1)retain需要使用的对象 (2)release之前的对象 (3)只有传入的对象和之前的不同才需要release和retain - (void)setRoom:(Room *)room{// 避免过度释放if (room != _room){// 对当前正在使用的房间(旧房间)做一次release[_room release];// 对新房间做一次retain操作 _room = [room retain];}}

dealloc方法的内存管理

- (void)dealloc{ // 当人不在了,代表不用房间了 // 对房间做一次release操作 [_room release]; [super dealloc];}

多个对象示例:

要求:

/*

微博类(Status)

文字内容(text)

配图(picture)

发表时间(createTime)

作者(author)

转发的说说(repostStatus)

评论数(commentCount)

转发数(retweetCount)

赞数(likeCount)

作者类(Author)

昵称(name)

头像(icon)

生日(birthday)

账号(account)

账号(Account)

账号名称(name)

账号密码(pwd)

账号注册时间(registerTime)

模拟场景:

老王在2010-1-1 17:56:34注册了一个账号

(名称:[email protected], 密码:haomage)

老王的生日是1986-3-8 18:18:18

老王发布一条说说

文字内容 @“爆米花手机比逼格更有逼格”

图片 @“phone.png”

发表时间: 2015-6-20 10:23:23

作者: 老王

被转发的说说: 没有

评论数: 100

转发数: 90

点赞数: 200

王大锤在2012-8-8 19:26:54注册了一个账号

(名称:[email protected], 密码:654321)

王大锤的生日是1989-9-6 14:16:28

王大锤在2015-6-21 20:47:09时,转发了张三之前发布的说说, 并且还附带了一句话:@“真的很有逼格”

*/

Account.h

#import <Foundation/Foundation.h>typedef struct {int year;int month;int day;int hour;int minute;int second;} MyDate;@interface Account : NSObject// 账号名称@property(nonatomic, retain)NSString *eaml;// 账号密码@property(nonatomic, retain)NSString *pwd;// 账号注册时间@property(nonatomic, assign)MyDate registerTime;@end

Account.m

#import "Account.h"@implementation Account- (void)dealloc {NSLog(@"%s", __func__);self.eaml = nil;self.pwd = nil;[super dealloc];}@end

Author.h

#import <Foundation/Foundation.h>#import "Account.h"@interface Author : NSObject// 昵称@property(nonatomic, retain)NSString *name;// 头像@property(nonatomic, retain)NSString *icon;// 生日@property(nonatomic, assign)MyDate birthday;// 账号@property(nonatomic, retain)Account *account;@end

Author.m

#import "Author.h"@implementation Author- (NSString *)description {return [NSString stringWithFormat:@"/n作者:%@/n", _name];}- (void)dealloc {NSLog(@"%s", __func__);self.name = nil;self.icon = nil;self.account = nil;[super dealloc];}@end

Status.h

#import <Foundation/Foundation.h>#import "Author.h"@interface Status : NSObject// 文字内容@property(nonatomic, retain)NSString *text;// 配图@property(nonatomic, retain)NSString *picture;// 发表时间@property(nonatomic, assign)MyDate createTime;// 作者@property(nonatomic, retain)Author *author;// 转发的微博@property(nonatomic, retain)Status *repostStatus;// 评论数@property(nonatomic, assign)int commentCount;// 转发数@property(nonatomic, assign)int retweetCount;// 赞数@property(nonatomic, assign)int likeCount;@end

Status.m

#import "Status.h"@implementation Status- (NSString *)description {return [NSString stringWithFormat:@"/n微博内容:%@/n发表时间:%d-%d-%d-%d-%d-%d/n评论:%d 转发:%d 点赞:%d",_text, _createTime.year, _createTime.month, _createTime.day, _createTime.hour, _createTime.minute, _createTime.second, _commentCount, _retweetCount, _likeCount];}- (void)dealloc {NSLog(@"%s", __func__);self.text = nil;self.picture = nil;self.repostStatus = nil;self.author = nil;[super dealloc];}@end

main.m

#import <Foundation/Foundation.h>#import "Status.h"int main(int argc, const char * argv[]) { @autoreleasepool { // 老王 // 1.创建账号 Account *lwAccount = [[Account alloc] init]; lwAccount.eaml = @"[email protected]"; lwAccount.pwd = @"haomage"; lwAccount.registerTime = (MyDate){2010, 1, 1, 17, 56, 34}; // 2.创建作者 Author *lwAuthor = [[Author alloc] init]; lwAuthor.name = @"老王"; lwAuthor.icon = @"lw.png"; lwAuthor.birthday = (MyDate){1986, 3, 8, 18, 18, 18}; lwAuthor.account = lwAccount; // 3.发微博 Status *lwStatus = [[Status alloc] init]; lwStatus.text = @"爆米花手机比逼格更有逼格"; lwStatus.picture = @"phone.png"; lwStatus.createTime = (MyDate){2015, 6, 20, 10, 23, 23}; lwStatus.author = lwAuthor; lwStatus.repostStatus = nil; lwStatus.commentCount = 100; lwStatus.retweetCount = 90; lwStatus.likeCount = 200; // 王大锤 Account *wdcAccount = [[Account alloc] init]; wdcAccount.eaml = @"[email protected]"; wdcAccount.pwd = @"654321"; wdcAccount.registerTime = (MyDate){2012, 8, 8, 19, 26, 54}; Author *wdcAuthor = [[Author alloc] init]; wdcAuthor.name = @"王大锤"; wdcAuthor.icon = @"wdc.png"; wdcAuthor.birthday = (MyDate){1989, 9, 6, 14, 16, 28}; wdcAuthor.account = wdcAccount; Status *wdcStatus = [[Status alloc] init]; wdcStatus.text = @"真的很有逼格"; lwStatus.picture = nil; wdcStatus.createTime = (MyDate){2015, 6, 21, 20, 47, 9}; wdcStatus.author = lwAuthor; wdcStatus.repostStatus = lwStatus; wdcStatus.commentCount = 0; wdcStatus.retweetCount = 0; wdcStatus.likeCount = 0; NSLog(@"%@%@", lwStatus, lwAuthor); [lwAccount release]; [lwAuthor release]; [lwStatus release]; [wdcAccount release]; [wdcAuthor release]; [wdcStatus release]; } return 0;} @property参数 控制set方法的内存管理

>retain : release旧值,retain新值(用于OC对象)

>assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)

>copy : release旧值,copy新值(一般用于NSString *)

控制需不需要生成set方法

>readwrite :同时生成set方法和get方法(默认)

>readonly :只会生成get方法

多线程管理

>atomic :性能低(默认)

>nonatomic :性能高

控制set方法和get方法的名称

>setter : 设置set方法的名称,一定有个冒号:

>getter : 设置get方法的名称

>注意: 不同类型的参数可以组合在一起使用

循环retian基本概念 循环retain的场景

>比如A对象retain了B对象,B对象retain了A对象

循环retain的弊端

>这样会导致A对象和B对象永远无法释放

循环retain的解决方案

>当两端互相引用时,应该一端用retain、一端用assign


相关阅读:
Top