龙空技术网

Objective-C编程内存管理之——MRC(手动引用计数)

核子力量 130

前言:

眼前咱们对“引用计数 js”大约比较看重,小伙伴们都想要学习一些“引用计数 js”的相关知识。那么小编同时在网上网罗了一些有关“引用计数 js””的相关知识,希望各位老铁们能喜欢,小伙伴们一起来了解一下吧!

一、概述

1、垃圾回收

在Objective-C中,类的对象存储在堆中、系统并不会自动释放其在堆中的内存(而基本类型数据则是存储在栈上、由系统自动管理),因此如果一个对象创建、使用后没有得到及时释放,那么它就会占用堆内存;其它高级语言(如:Java)都是通过垃圾回收(GC)机制来自动管理对象内存的,但Objective-C并没有类似的垃圾回收机制、而是使用一套自己专有的内存管理机制——根据其历史发展,先后主要产生了MRC、ARC两种内存管理机制,本案例主要介绍MRC的相关内容

2、MRC及ARC

在Objective-C中,每个对象内部都有一个与之对应的整数(retainCount)——引用计数(Reference Counting);如果由开发人员通过手工控制每个对象的“引用计数”的增减来管理对象内存,这种机制我们就称为MRC(Manual Reference Counting)、也叫“手工引用计数”;如果由系统通过自动控制每个对象的“引用计数”的增减来管理对象内存,这种机制我们就称为ARC(Automatic Reference Counting)、也叫“自动引用计数”;MRC主要被一些早期项目(Xcode4.2版本之前)所采用,当然如果由于某些原因你需要特意创建一个MRC项目也是可以的

3、MRC设置

Xcode4.2版本开始引入ARC机制、并默认设置为该机制,Xcode4.2之前的版本默认为MRC机制;因此,如果你使用的是Xcode4.2之后的版本、而又要使用MRC机制,你必须先人工关闭ARC机制(否则,在你编写手工释放对象代码时,Xcode会报错),具体操作步骤如下:

项目属性—>Build Settings->搜索“garbage”、找到“Objective-C Automatic Reference Counting”设置为"No",这样项目就采用MRC内存管理机制了

二、原理原则

1、基本原理

一个对象在被创建之后,它的引用计数为1;当调用这个对象的alloc、retain、new、copy等方法之后,该对象的引用计数将在原来的基础上自动加1;当调用这个对象的release方法之后,该对象的引用计数将自动减1;当一个对象的引用计数变为0时,系统将会自动调用该对象的dealloc方法来销毁该对象、释放其占用的内存

2、主要原则

原则1、谁创建、谁释放

由于对象的引用往往错综复杂,基于MRC的内存管理往往非常困难(如:一不小心就可能出现内存泄漏问题),此时就需要遵循一个原则“谁创建、谁释放”——即:谁创建的对象,该对象就由谁来负责释放,这样内存的管理将变得相对容易很多

原则2、覆盖dealloc方法

在定义一个类时,要注意通过重写来覆盖继承自父类的dealloc方法,主要原因如下:

原因1:该类可能有实例变量、属性需要手工释放

如果该类的实例变量使用了alloc、retain、new、copy、mutableCopy等方法,则必须进行手工释放;如果该类的属性使用了retain、copy等参数修饰,则必须进行手工释放

原因2:需要调用父类的dealloc方法释放父类对应的内存

父类可能有其它引用对象需要释放,而且当前对象真正的释放操作是在父类的dealloc中完成的

3、案例演示

//

// FieldEntity.h

//

#import<Foundation/Foundation.h>

/**

* 字段实体类;作为样例实体的成员变量,用于案例演示

*/

@interfaceFieldEntity : NSObject

/**

* 字段名称

*/

@property (copy) NSString *name;

/**

* 构造方法(有参)

* @param name [IN]字段名称

* @return

* 被构造对象

*/

- (id)initWithName:(NSString *)name;

/**

* 输出名称

*/

- (void)printName;

@end

//

// FieldEntity.m

//

#import "FieldEntity.h"

@implementationFieldEntity

/**

* 构造方法(有参)

* @param name [IN]字段名称

* @return

* 被构造对象

*/

- (id)initWithName:(NSString *)name

{

//构造方法中可用self关键字调用类的另一个构造方法

self=[self init];

if(self)

{

printf("执行字段实体(%s)的构造方法!\n",[name UTF8String]);

self.name = name;

}

return self;

}//- (id)initWithName:(NSString *)name

/**

* 输出名称

*/

- (void)printName

{

printf("执行字段实体(%s)的输出名称方法:我是%s\n",[self.name UTF8String],[self.name UTF8String]);

}//- (void)printName

/**

* 析构方法(重写[NSObjectdealloc]方法)

*/

- (void)dealloc

{

printf("执行字段实体(%s)的析构方法!\n",[self.name UTF8String]);

//释放父类对应的内存

[superdealloc];

}//- (void)dealloc

@end

//

// PrincipleExample.h

//

#import<Foundation/Foundation.h>

//引入字段实体类相关定义

#import "FieldEntity.h"

/**

* 原理原则样例类;用于原理原则案例演示

*/

@interfacePrincipleExample : NSObject

{

/**

* 字段实体

*/

FieldEntity *fieldEntity;

}

/**

* 样例名称

*/

@property (copy) NSString *name;

/**

* 构造方法(有参)

* @param name [IN]样例名称

* @return

* 被构造对象

*/

- (id)initWithName:(NSString *)name;

/**

* 字段实体的setter方法

* @param field [IN]字段实体

*/

- (void)setFieldEntity:(FieldEntity *)field;

/**

* 字段实体的getter方法

* @return

* 字段实体

*/

- (FieldEntity *)fieldEntity;

@end

//

// PrincipleExample.m

//

#import "PrincipleExample.h"

@implementationPrincipleExample

/**

* 构造方法(有参)

* @param name [IN]样例名称

* @return

* 被构造对象

*/

- (id)initWithName:(NSString *)name

{

//构造方法中可用self关键字调用类的另一个构造方法

self=[self init];

if(self)

{

printf("执行样例实体(%s)的构造方法!\n",[name UTF8String]);

self.name = name;

}

return self;

}//- (id)initWithName:(NSString *)name

/**

* 字段实体的setter方法

* @param field [IN]字段实体

*/

- (void)setFieldEntity:(FieldEntity *)field

{

//参数对象和当前成员变量不是同一个对象

if (self->fieldEntity != field)

{

//释放之前的对象

[self->fieldEntity release];

//赋值时,参数对象首先执行retain方法(引用计数加1)

self->fieldEntity = [field retain];

}

}//- (void)setFieldEntity:(FieldEntity *)field

/**

* 字段实体的getter方法

* @return

* 字段实体

*/

- (FieldEntity *)fieldEntity

{

//直接返回字段实体

return self->fieldEntity;

}//- (FieldEntity *)fieldEntity

/**

* 析构方法(重写[NSObjectdealloc]方法)

*/

- (void)dealloc

{

printf("执行样例实体(%s)的析构方法!\n",[self.name UTF8String]);

//释放对象(即使对象为nil也没关系,Objective-C给空对象发送消息不会响应、也不会引起错误)

[self->fieldEntity release];

//释放父类对应的内存

[superdealloc];

}//- (void)dealloc

@end

//

// main.m

//

//引入原理原则样例类相关定义

#import "PrincipleExample.h"

int main(intargc, const char * argv[])

{

//创建原理原则样例类对象;调用alloc方法,被创建对象的引用计数+1

PrincipleExample *principleExample = [[PrincipleExamplealloc] initWithName:@"样例1"];

//创建字段实体;调用alloc方法,被创建对象的引用计数+1

FieldEntity *fieldEntity1=[[FieldEntityalloc] initWithName:@"字段1"];

FieldEntity *fieldEntity2=[[FieldEntityalloc] initWithName:@"字段2"];

printf("字段实体(%s)的引用计数为:%lu\n"

,[fieldEntity1.name UTF8String]

,[fieldEntity1 retainCount]);

//调用setter方法对字段实体赋值;此时由于在setFieldEntity方法内部对fieldEntity1对象调用了retain方法,

//所以fieldEntity1的引用计数+1

principleExample.fieldEntity = fieldEntity1;

printf("字段实体(%s)的引用计数为:%lu\n"

,[fieldEntity1.name UTF8String]

,[fieldEntity1 retainCount]);

//释放字段实体;对象调用release方法,调用对象的引用计数-1

[fieldEntity1 release];

printf("字段实体(%s)的引用计数为:%lu\n"

,[fieldEntity1.name UTF8String]

,[fieldEntity1 retainCount]);

//变量置空(具体原因同下文的“exampleEntity = nil”说明)

fieldEntity1=nil;

//释放字段实体;对象调用release方法,调用对象的引用计数-1;减1后由于此时引用计数变为0,系统将自动调用对象的析构方法

[fieldEntity2 release];

//变量置空(具体原因同下文的“exampleEntity = nil”说明)

fieldEntity2=nil;

//字段成员输出其名称

[principleExample.fieldEntityprintName];

//释放样例实体;对象调用release方法,调用对象的引用计数-1;减1后由于此时引用计数变为0,系统将自动调用对象的析构方法

//;在ExampleEntity类的析构方法中再释放成员变量(字段1)

[principleExample release];

//变量置空;上面调用过release方法,变量exampleEntity指向的对象就会被销毁,但是此时变量exampleEntity中还存放着原

//对象的地址,如果不置空,则exampleEntity就是一个野指针(它指向的内存可能已不属于这个程序,对其操作会很危险);置空

//后即使再对exampleEntity操作也没关系,因为Objective-C中给空对象发送消息不会响应、也不会引起错误

principleExample = nil;

}

案例运行结果如下:

三、属性参数

1、参数格式

Objective-C中对属性的操作(setter/getter方法),其实是对相应实例变量的操作,其操作过程不仅会涉及setter/getter方法的自动生成,还可能涉及到线程安全及内存管理的问题;针对这3方面的问题,我们可以在定义属性时通过为其设置属性参数来解决,其格式如下:

@property (参数1,参数2,参数3) 类型名称;

其中,参数最多可以有3个(分别代表3类:原子性、读写性、内存管理),参数中间用逗号分隔、参数顺序不固定,每类参数最多可选1个;如果不设置参数,系统将使用默认参数:(atomic,readwrite,assign);如果3类参数未设全,未设的类别将使用默认参数

2、参数类型

对于属性参数的3个类别,具体说明如下:

类别1、原子性

atomic:对属性加锁,多线程环境下线程安全;该类别的默认值

nonatomic:对属性不加锁,多线程环境下非线程安全,但访问速度快

类别2、读写性

readwrite:同时自动生成setter及getter方法;该类别的默认值

readonly:仅自动生成getter方法

类别3、内存管理

assign/unsafe_unretained:在setter方法中,直接赋值;该类别的默认值;可用于任何类型、但非类类型只能使用assign,自动生成的示例代码如下:

-(void)setX:(int)x

{

_x = x;

}

retain:在setter方法中,先释放原对象(调用release方法)、再保持新赋值对象(调用retain方法);可用于任何类类型,但通常用于非字符串的类类型,自动生成的示例代码如下:

-(void)setX:(类类型 *)x

{

if(_x != x)

{

[_x release];

_x = [x retain];

}

}

copy:在setter方法中,先释放原对象(调用release方法)、再拷贝出一个新赋值对象(调用copy方法);通常用于NSString、NSArray、NSDictionary、NSSet、NSData等可深复制的类类型,不能用于非类类型,自动生成的示例代码如下:

-(void)setX:(类类型 *)x

{

if(_x != x)

{

[_x release];

_x = [x copy];

}

}

3、案例演示

//

// MemberEntity.h

//

#import<Foundation/Foundation.h>

/**

* 成员实体类

*/

@interfaceMemberEntity : NSObject

@end

//

// MemberEntity.m

//

#import "MemberEntity.h"

@implementationMemberEntity

@end

//

// PropertyExample.h

//

#import<Foundation/Foundation.h>

//引入成员实体类相关定义

#import "MemberEntity.h"

/**

* 属性样例类;用于属性参数案例演示

*/

@interfacePropertyExample : NSObject

{

@public

/**

* 属性对应的实例变量

*/

int _member1;

MemberEntity *_member2;

NSString *_member3;

}

/**

* 属性定义

*/

@property (nonatomic,readwrite,assign) int member1;

@property (nonatomic,readwrite,retain) MemberEntity *member2;

@property (nonatomic,readwrite,copy) NSString *member3;

@end

//

// PropertyExample.m

//

#import "PropertyExample.h"

@implementationPropertyExample

@synthesize member1 = _member1;

@synthesize member2 = _member2;

@synthesize member3 = _member3;

@end

//

// main.m

//

//引入属性样例类相关定义

#import "PropertyExample.h"

int main(intargc, const char * argv[])

{

//创建属性样例类对象

PropertyExample *propertyExample = [[PropertyExamplealloc] init];

//非类类型属性

propertyExample.member1 = 11;

if (11 == propertyExample->_member1)

{

printf("对于非类类型属性,其内存管理特性参数只能设置为:assign/unsafe_unretained\n");

}

//非字符串的类类型

MemberEntity *memberEntity1 = [[MemberEntityalloc] init];

propertyExample.member2 = memberEntity1;

if (memberEntity1 == propertyExample->_member2)

{

printf("对于非字符串的类类型属性,其内存管理特性参数可设置为:assign/unsafe_unretained、retain,但不能设"

"置为copy(将会出现编译运行异常)!\n");

}

//字符串类型

NSString *strTemp = @"test";

propertyExample.member3 = strTemp;

if (strTemp == propertyExample->_member3)

{

printf("对于字符串类型属性,其内存管理特性参数可设置为:assign/unsafe_unretained、retain、copy,但一般"

"建议设置为copy\n");

}

//释放属性样例类对象

[propertyExample release];

propertyExample = nil;

}

案例运行结果如下:

四、自动释放池

1、原理阐述

在MRC环境下,自动释放池相当于一种半自动化的内存释放机制,其间部分操作还是需要开发人员手动完成;自动释放池是使用@autoreleasepool关键字声明的一个代码块(如:@autoreleasepool{}),如果一个自动释放池中的对象在调用了autorelase方法,那么当代码块执行完(此时自动释放池将被系统清空销毁)之后,在块中调用过autorelease方法的对象都会自动调用一次release方法,这样我们就达到了对象内存自动释放的目的,同时对象的销毁过程也得到了延迟(最后统一调用release方法)

2、技术要点

要点1、autorelease方法不会改变对象的引用计数,只是将对象放到自动释放池中

要点2、自动释放池实质是当自动释放池销毁后调用对象的release方法,但不一定就能销毁对象(如:如果一个对象调用release方法后,其引用计数仍然大于1,此时该对象就无法被销毁)

要点3、由于自动释放池是在最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者单个对象占用内存就比较多),那么该操作最好不要放到自动释放池(或者考虑放到多个自动释放池中去实现)

要点4、如果要使自动释放池中的对象在自动释放池被清空销毁后还能存在,那么该对象需要使自己在自动释放池被清空销毁后自身的引用计数仍大于0(比如:在自动释放池销毁之前调用retain方法)

要点5、当应用终止时,当前进程在内存中的所有对象都会被释放,无论它们是否在自动释放池中

3、案例演示

//

// AutoReleaseExample.h

//

#import<Foundation/Foundation.h>

/**

* 自动释放样例类;用于自动释放池案例演示

*/

@interfaceAutoReleaseExample : NSObject

/**

* 样例名称

*/

@property (copy) NSString *name;

/**

* 构造方法(有参)

* @param name [IN]样例名称

* @return

* 被构造对象

*/

- (id)initWithName:(NSString *)name;

/**

* 用指定名称创建一个样例对象(类方法)

* @param name [IN]样例名称

* @return

* 被创建对象

*/

+ (AutoReleaseExample *)createWithName:(NSString *)name;

@end

//

// AutoReleaseExample.m

//

#import "AutoReleaseExample.h"

@implementationAutoReleaseExample

/**

* 构造方法(有参)

* @param name [IN]样例名称

* @return

* 被构造对象

*/

- (id)initWithName:(NSString *)name

{

//构造方法中可用self关键字调用类的另一个构造方法

self=[self init];

if(self)

{

printf("执行自动释放样例(%s)的构造方法!\n",[name UTF8String]);

self.name = name;

}

return self;

}//- (id)initWithName:(NSString *)name

/**

* 用指定名称创建一个样例对象(类方法)

* @param name [IN]样例名称

* @return

* 被创建对象

*/

+ (AutoReleaseExample *)createWithName:(NSString *)name

{

//创建样例对象(注意这里调用了autorelease方法)

AutoReleaseExample *example = [[[AutoReleaseExamplealloc]initWithName:name] autorelease];

return example;

}//+ (AutoReleaseExample *)createWithName:(NSString *)name

/**

* 析构方法(重写[NSObjectdealloc]方法)

*/

- (void)dealloc

{

printf("执行自动释放样例(%s)的析构方法!\n",[self.name UTF8String]);

//释放父类对应的内存

[superdealloc];

}//- (void)dealloc

@end

//

// main.m

//

//引入属性样例类相关定义

#import "PropertyExample.h"

int main(intargc, const char * argv[])

{

AutoReleaseExample *example4 = nil;

//自动释放池

@autoreleasepool

{

//创建样例1

AutoReleaseExample *example1 = [[AutoReleaseExamplealloc] init];

//调用了自动释放方法autorelease,后面就不需要手动调用release方法进行释放了

[example1autorelease];

//由于autorelease方法是延迟释放,所以这里仍然可以使用example1

example1.name=@"样例1";

//创建样例2(同时调用了自动释放方法autorelease)

[[[AutoReleaseExamplealloc] initWithName:@"样例2"] autorelease];

//创建样例3(createWithName方法内部已经调用了自动释放方法autorelease)

[AutoReleaseExamplecreateWithName:@"样例3"];

//创建样例4(同时调用了自动释放方法autorelease)

example4 = [[[AutoReleaseExamplealloc] initWithName:@"样例4"] autorelease];

//引用计数+1

[example4 retain];

}//@autoreleasepool

//在自动释放池销毁后(样例4还未被释放),再被手动释放

[example4 release];

}

案例运行结果如下:

标签: #引用计数 js