您的位置:首页 > 移动开发 > Objective-C

《Objective-C高级编程 iOS与OS X多线程和内存管理》学习笔记——第一章自动引用计数

2016-08-01 21:47 549 查看
一、什么是自动引用计数

顾名思义,自动引用计数(ARC)是指内存管理中对引用采取自动计数的计数,以下摘自苹果官方的说明:

在objective-c中采用ARC机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,着在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。

ARC模式需要满足以下条件:

使用xcode4.2或以上版本;
使用LLVM编译器3.0或以上版本;
编译器选项中设置ARC为有效;

二、内存管理/引用计数

1、概要

2、内存管理的思考方式

自己生成的对象,自己所持有(alloc,new,copy,mutableCopy);
非自己生成的对象,自己也能持有(retain);
不再需要自己持有的对象要释放(release,dealloc);
非自己持有的对象无法释放;



有关objective-c内存管理的方法,实际上不包括在该语言中,而是包含在cocoa框架中用于OS X、iOS应用开发。cocoa框架中foundation框架类库的NSObject担负内存管理的职责。objective-c内存管理中的 alloc/retain/release/dealloc 方法分别指向 NSObject 类的alloc类方法、retain类方法、release实例方法和dealloc实例方法;



/*
自己生成的对象,自己所持有;
使用一下名称开头的方法名意味着自己生成的对象只有自己持有:
*alloc
*new
*copy
*mutableCopy
*/

//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
/*
非自己生成的对象,自己也能持有;
*/

//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但自己不持有对象
[obj retain];
//自己持有对象
/*
不再需要自己持有的对象时释放;
自己持有的对象,一旦不再需要,持有者有义务释放该对象。释放使用release方法;
*/

//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
[obj release];
//释放对象,指向对象的指针仍然被保留在变量obj中,貌似能够访问,但事实上对象一经释放绝对不能访问,访问会导致程序崩溃;
/*
无法释放非自己持有的对象;
如果在应用程序中释放了非自己持有的对象就会造成崩溃;
*/

//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
[obj release];
//对象已释放
[obj release];
//释放之后再次释放已非自己持有的对象,程序崩溃


3、alloc/retain/release/dealloc实现

对象的引用计数可通过retainCount实例方法取得;

id obj = [[NSObject alloc] init];
NSLog(@"retainCount = %d", [obj retainCount]);
//显示结果:retainCount = 1


在GNUsetp中的实现,具体总结如下:

在objective-c的对象中存在引用计数这一整数值;
调用alloc或是retain方法后,引用计数值加1;
调用release后,引用计数值减1;
引用计数值为0时,调用dealloc方法废弃对象;

4、苹果的实现

+alloc
+allocWithZone:
class_createInstance
calloc

alloc类方法首先调用allocWithZone:类方法,然后调用class_createInstance函数,最后通过调用calloc分配内存块;

苹果的实现大概就是采用散列表(引用计数表)来管理引用计数;

GNUstep将引用计数保存在对象占用内存块头部的变量中,苹果的实现,则是保存在引用计数表的记录中;

(GNUstep)通过内存块头部管理引用计数的好处如下:

少量代码即可完成;
能够统一管理引用计数内存块与对象用内存块;

(苹果)通过引用计数表管理计数的好处如下:

对象用内存块的分配无需考虑内存块头部;
引用计数表各记录中有内存块地址,可从各个记录追溯到各对象的内存块;

5、autorelease

{
int a;
}
/*
因为超出变量作用域,自动变量“int a”被废弃,不可再访问;
*/


autorelease会像C语言的自动变量那样来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法被自动调用;

autorelease的具体使用方法如下:

生成并持有NSAutoreleasePool对象;
调用已分配对象的autorelease实例方法;
废弃NSAutoreleasePool对象;



NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法,用源代码表示如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
//最后一行“[pool drain]”等同于"[obj release]"


在cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。因此,应用程序开发者不一定非得使用NSAutoreleasePool对象来进行开发工作。



6、autorelease实现

//GNUstep/modules/core/base/Source/NSObject.m autorelease
-(id)autrelease
{
[NSAutoreleasePool addObject:self];
}


autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法;

7、苹果的实现

三、ARC规则

1、概要

同一个程序中按文件单位可以选择ARC 有效/无效;

设置ARC有效的编译方法如下:

使用clang(LLVM编译器)3.0或以上版本;
指定编译器属性为“-fobc-arc”;

xcode4.2默认设定为所有的文件ARC有效;

2、内存管理的思考方式

自己生成的对象,自己所持有;
非自己生成的对象,自己也能持有;
自己持有的对象不再需要时释放;
非自己持有的对象无法释放;

3、所有权修饰符

ARC有效时,id类型和对象类型同C语言其他类型不同,其类型
4000
上必须附加所有权修饰符,所有权修饰符一共有4种:

__strong 修饰符
__weak 修饰符
__unsafe_unretained 修饰符
__autoreleasing 修饰符
__strong 修饰符:

__strong修饰符是id类型和对象类型默认的所有权修饰符,在没有明确指定所有权修饰符时,默认为__strong修饰符,代码如下:

id obj = [[NSObject alloc] init];
//等价于以下代码
id __strong obj = [[NSObject alloc] init];
//上述源代码在ARC无效时的表述
id obj = [[NSObject alloc] init];
//ARC无效
{
id __strong obj = [[NSObject alloc] init];
}
//该源代码等价于下列代码
{
id obj = [[NSObject alloc] init];
[obj release];
}


如上述代码所示,附加__strong 修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象;

__strong修饰符表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放;

{
//自己生成并持有对象
id __strong obj = [[NSObject alloc] init];
//因为变量obj为强引用,所以自己持有对象
}
//因为变量Obj超出其作用域,强引用失效,所以自动地释放自己持有的对象,对象的所有者不存在,因此废弃该对象
{
//取得非自己生成并持有对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象
}
//因为变量obj超出其作用域,强引用失效,所以自动地释放自己持有的对象


“自己生成的对象,自己持有”和“非自己生成的对象,自己也能持有”只需通过对带__strong 修饰符的变量赋值便可达成。通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所需对象废弃)或对变量赋值,都可以做到“不再需要自己所持有的对象时释放”。“非自己持有的对象无法释放”,由于不必再次键入release,所以原本就不会执行。

因为id类型和对象类型的所有权修饰符默认为__strong修饰符,所以不需要写上“__strong”;

__weak修饰符:

循环引用容易发生内存泄漏,所谓内存泄漏就是应该废弃的对象在超出其生存周期后继续存在;对象持有其自身时(指针指向自身),也会发生循环引用;

__weak修饰符与__strong修饰符相反,提供弱引用。强引用和弱引用广义区别:

强引用的存亡直接影响对象是否被释放,当修饰对象超出作用域,强引用失效,对象随之被释放;
弱引用除了不能决定对象是否被释放之后,其他跟强引用一样。不管对象是否被弱引用修饰,只要没有强引用指向该对象,对象就会被释放;

在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)。

__unsafe_unretained 修饰符:

尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象;

__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放;

赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,程序就会崩溃;

__autoreleasing 修饰符:

ARC有效autorelease不能使用,也不能使用NSAutoreleasePool类;

//ARC有效时,源代码也能写成下面这样
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}

在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有 __autoreleasing 修饰符的变量替代 autorelease 方法;
以下总结默认基于ARC无效情况下,如果是基于ARC有效的情况下,会说明;

(1)不管这个对象是在自动释放池内还是外部创建的,当你向一个对象发送一个autorelease消息时,cocoa就会将该对象放入到最近的自动释放池中,并返回一个self;自动释放池结束销毁时(drain),统一对里面的对象调用release方法,引用计数减1;

(2)@autoreleasepool可以嵌套使用;

(3)对象执行autorelease方法后自身引用计数不会改变,而且会返回对象本身,当autoreleasepool销毁时,池中的对象引用计数才会减1;

(4)autoreleasepool存储于内存中的栈中,遵循”先进后出“原则;

//自动释放池1
@autoreleasepool{
Person *person = [[[Person alloc] init] autorelease];

//自动释放池2
@autoreleasepool{
Person *person2 = [[[Person alloc] init] autorelease];
}
}
(5)如果把一个对象重复加到自动释放池如[person autorelease];[person autorelease];那么会出错。调用几次autorelease方法,在自动释放池中就释放几次,第一次自动释放对象引用计数为0,已经被销毁;第二次释放时,person变成了野指针;
(6)在ARC机制中,我们用@property声明的成员变量,建议用strong修饰符;

(7)autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把对象添加到最近的autoreleasepool中,当该pool被释放时,该pool中的所有对象会被调用release方法;

4、规则

不能使用 retain/release/retainCount/autorelease
不能使用 NSAllocateObject/NSDeallocateObject
须遵守内存管理的方法命名规则
不要显示调用dealloc
使用 @autorelease 块代替NSAutoreleasePool
不能使用区域(NSZone)
对象型变量不能作为C语言结固体的成员
显示转换“id”和"void *"

不要显示调用dealloc:

无论ARC是否有效,只要对象的所有者都不持有对象,该对象就被废弃。对象被废弃时,不管ARC是否有效,都会调用对象的dealloc方法:

dealloc方法在大多数情况下还适用于删除已注册的代理或观察者对象;

-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}


在ARC无效时必须像下面这样调用[super dealloc];

-(void)dealloc{
[super dealloc];
}


ARC有效时会遵循无法显示调用dealloc这一规则,如果使用就会同release等方法一样,引起编译错误;

5、属性

当ARC有效时,以下可作为这种属性声明转给你使用的属性来用:



6、数组

四、ARC的实现

1、__strong修饰符

2、__weak修饰符

若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给对象;
使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象;

3、__autoreleasing修饰符

4、引用计数

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS自动引用计数
相关文章推荐