《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 自动引用计数

一、自动引用计数

1.1 内存管理的思考方式

对象操作与objective-c 方法的对应
对象操作Object-C  方法
生成并持有对象alloc、new、copy、mutableCopy
持有对象retain方法
释放对象release方法
废弃对象dealloc方法

        

 

1.1.1 自己生成的对象,自己持有

使用以下名称开头的方法一位着自己生成的对象只有自己持有: alloc  |new | copy |mutableCopy

使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋值给变量obj. 使用new类方法也能生成并持有对象,

[NSOject new] == [[NSObject alloc] init]

copy 方法基于NSCooying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。

与copy方法类似,mutableCopyWithZone:方法生成并持有对象的副本。

两者区别在于copy方法生成不可变更的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc、new方法一样,在“自己生成并持有对象”这点上没有改变。

1.1.2 非自己生成的对象,自己也能持有

除 alloc  |new | copy |mutableCopy 之外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者

eg1:

// 取得非自己生成并持有的对象 取得的对象存在,但自己不持有对象
id obj = [NSMutableArray array];
// NSMutableArray类对象被赋给变量obj,但变量obj自己并不持有该对象,使用retain方法可以持有对象。
// 自己持有对象
[obj retain];
//通过retain 方法,非自己生成的对象跟用alloc、new、cooy、mutableCopy方法生成并持有的对象一样,成为了自己所持有的。

1.1.3 不再需要自己持有的对象时释放

自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法

// 释放对象,对象不可再被访问
[obj release];

若要用某个方法生成对象,并将其返还给该方法的调用方?

  • 根据命名规则,这些用来取得谁都不持有的对象的方法不能以alloc/ new / copy/ mutableCopy
- (id) allObject{
    id  obj = [NSObject alloc] init];
    return obj;
}
id obj1 = [obj0 allObject];

如上例所示:原封不动低返回用alloc方法生成并持有的对象,就能让调用方也持有该对象。

调用[NSMutableArray array] 方法使取得的对象存在,但自己不持有对象,如何实现?

- (id) object{
    id obj = [NSObject alloc ]init];
    [obj autorelease];
    // autorelease提供这样的功能,使对象在超出指定的生存范围时你那个自动并正确地释放(调用realse方法
    return obj;
}

1.1.4 无法释放非自己持有的对象

对于持有者是自己的对象,不需要该对象时,需将其释放。

除此之外之外所得到的对象(非自己持有的对象)以及 自己持有对象后 释放过的对象,释放时会导致奔溃;

1.2  alloc/ retain/ release/ dealloc 实现 

p13

alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置为0返回。

执行alloc 后的对象的retainCout是1

由对象寻址找到对象的内存头部,从而访问其中的retained变量。

  • 由O调用bject - C对象中存有引用计数这一整数值。
  • 调用 alloc 或是 retain 方法后,引用计数值加1。
  • 调用release后,引用计数值减1。
  • 引用计数值为0时,调用dealloc方法废弃对象。

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

1.3 autorelease

autorelease会像C语言的自动变量那也来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法被调用。另外,同c语言不同的是,编程人员可以设定变量的作用域。

autorelease 的具体使用方法如下:

  1.  生成并持有NSAutoreleasePool对象;
  2. 调用已分配对象的autorelease方法;
  3. 废弃NSAutoreleasePool对象。

1.4 所有权修饰符

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

_strong、_weak、_unsafe_unretained、 _autoreleasing

_strong修饰符是id类型和对象类型默认的所有权修饰符。即:下面两种方式相同。表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

ARC有效时:

id obj = [[NSObject alloc] init]; 
id _strong obj = [[NSObject alloc] init];

ARC无效时:

{
    id obj = [[NSObject alloc] init]; 
    [obj release];
}

为了释生成并持有的对象,增加了调用release方法的代码。改代码进行的动作同先前ARC有效时的动作完全一样。

取得非自己生成并持有的对象,因为变量obj为强引用,所以自己持有对象。当变量obj超出其作用域,强引用失效,所以自动低释放自己所持有的对象,对象的所有者不存在,因此废弃该对象。

id _strong obj = [NSMutable array];

附有_strong修饰符的变量之间可以相互赋值

id  _strong obj0 = [NSObject alloc]init]; // obj0持有对象A的强引用
id  _strong obj1 = [NSObject alloc]init]; // obj1持有对象B的强引用
id  _strong obj2 = nil;  // obj2不持有任何对象
obj0 = obj1; // obj0持有由obj1赋值的对象B的强引用。因为obj0被赋值,所以原先持有的对对象A的强引用失效,对象A的所有者不存,因此废弃对象A。
// 此时,持有对象B的强引用的变量为obj0和object1
obj2 = obj0; // obj2持有由obj0赋值的对象B的强引用。
//此时,持有对象B的强引用的变量为obj0,obj1和obj2
obj1 = nil; //因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量为obj0和obj2
obj0 = nil;//因为nil被赋予了obj0,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量obj2
obj2 = nil;//因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,象B的所有值不存在,因此废弃对象B

_weak

_strong会造成循环引用 

        

@interface Test : NSObject{
    id _strong obj_;
}
- (void)setObject:(id _strong)obj;
@end
@implementation Test
- (id)init{
    self = [super init];
    return self;
}
- (void)setObject:(id _strong)obj{
    obj_ = obj;
}
@end
{
    id test0 = [[Test alloc] init]; // test0 持有Test对象A的强引用
    id test1 = [[Test alloc] init]; // test1 持有Test对象B的强引用
    [test0 setObject:test1];  //Test对象A的obj_成员变量持有Test对象B的强引用
    //此时,持有Test对象B的强引用的变量为 Test对象A的obj_和test1
    [test1 setObject:test0]; //Test对象B的obj_成员变量持有Test对象A的强引用
    //此时,持有Test对象A的强引用的变量为 Test对象B的obj_和test0
}
因为test0变量超出其作用域,强引用失效,所以自动释放Test对象A
因为test1变量超出其作用域,强引用失效,所以自动释放Test对象B
此时持有Test对象A的强引用的变量为Test对象B的obj_
此时持有Test对象B的强引用的变量为Test对象B的obj_
!!!发送内存泄漏

循环引用容易发送内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。

此代码的本意是赋予变量test0()的对象A和赋予变量test1的对象B在超出其变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是,循环引用使得对象不能再次废弃。

循环引用 ->自引用

id test = [Test alloc]init];
[test setObject:test];

_weak修饰符与_stong修饰符相反,提供弱引用,弱引用不能

{
    id _strong obj0 = [[NSObject alloc]init]; //自己生成并持有对象,因为obj0变量为强引用,所以自己持有对象。 
    id _weak obj1 = obj0; //obj1变量持有生成对象的弱引用 
}
因为变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为所有者不在,所以废弃该对象。

因为带 _weak 修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像下面这样将先前可能发生循环引用的类成员变量改成附有 _weak修饰符的成员变量的话,该现象可避免。

@interface Test : NSObject{
    id _weak obj_;
}
- (void)setObject:(id _strong)obj;
@end

_weak修饰符还有另一个优点。在持有某对象的弱引用时,该对象被废弃,则此引用将自动失效且处于nil被赋值的状态(空弱引用)。如下代码所示:

id  _weak obj1 = nil;
{
    id _strong obj0 = [[NSObject alloc] init];//自己生成并持有对象,因为obj0变量为强引用,所以自己持有对象。 
    obj1 = obj0;//obj1变量持有生成对象的弱引用
}
因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为无所有者,所以废弃该对象。废弃所有者的同时,持有该对象弱引用的onj变量的弱引用失效,nil赋值给obj1.

使用 _weak修饰符可避免循环引用。通过检查附有_weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

_autoreleasing修饰符

ARC 有效时,autorelease如何?

不能使用autolease方法,也不能使用NSAutoreleasePool类。但其实autolease功能起作用的。

//ARC无效
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];
//ARC无效
@autoreleasepool{
    id _autoreleasing obj = [NSObject alloc] init];
}

指定"@autoreleasepool 块"来替代“NSAutoreasePool类对象生成、持有以及废弃”这一范围。

ARC有效时,要通过将对象赋值给附加了_autoreleasing修饰符变量来替代调用autoreleasing方法。对象赋值给附有_autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。

即在ARC有效时,用@autoreleasepool 块"来替代“NSAutoreasePool类,用附有_autoreleasing修饰符的变量替代autorelease方法。

取得非自己生成并持有的对象时,如下所示:虽然可以使用alloc、new、copy、mutableCopy以外的方法来取得对象,但该对象已被注册到了autoreleasepool。

这同在ARC无效时取得调用了autorelease方法得对象是一样的。

这是由于编译器会检查方法名是否以alloc、new、copy、mutableCopy开始,如果不是自动将返回值的对象注册到autoreleasepool. 

 !!! 要遵守内存管理方法命名规则,init方法返回值的对象不注册到autoreleasepool。

@autoreleasepool{
    id _strong obj = [MSMutable array];//取得非自己生成并持有的对象
    //因为变量obj为强引用,所以自己持有对象。并且该对象由编译器判断其方法后,自动注册到autoreleasepool
}
// 因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象。
// 同时,随着@autoreleasepool 块的结束,注册到autoreleasepool中的所有对象被自动释放。因为对象的所有者不存在,所以废弃对象。

以上是不使用 _autoreleasing修饰符也能使对象注册到autoreleasepool。以下为取得非自己生成并持有对象时被调用方法:

+ (id) array{
    id obj = [[NSMutableArray alloc] init];
    return obj;
}

因为没有显示指定所有权修饰符,所以 id obj 同附有 _strong 修饰符的id_strong obj 是完全一样的。

由于return 使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放。但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。

以下为使用_weak修饰符的例子。虽然_weak修饰符是为了避免循环引用而使用的,但在访问附有_weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。

id _weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
==========================
id _weak obj1 = obj0;
id _autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);

为什么在访问附有_weak修饰符的变量必须注册到autoreleasepool的对象?

因为_weak修饰符只持有对象的弱引用,而在访问对象的过程中,该对象可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此在使用附有_weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象。

id的指针或对象的指针会默认附加上_autoreleasing修饰符,所以以下代码相等:

- (BOOL) performOperationWithError:(NSError *)error;
========================
- (BOOL) performOperationWithError:(NSError *_autoreleasing *)error;

作为alloc /new /copy /mutableCopy 方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。

因此,使用附有_autoreleasing修饰符的变量作为对象取得参数,与除alloc /new /copy /mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。

_unasfe_unretained修饰符

不安全的所有权修饰符,所修饰的变量不属于编译器的内存管理对象。同_weak 一样,自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即释放。

在使用_unasfe_unretained修饰符时,赋值给附有_strong修饰符的变量时,有必要确保被复制的对象确实存在。否则会奔溃。

1.5 规则

dealloc 中只需记述废弃对象时所必须的处理。

1.6 属性

当ARC有效时,以下可作为这种属性声明中使用的属性来用。

1.7 ARC的实现

1.7.1 _strong修饰符

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值