OC高级编程 第一章:自动引用计数

1.1什么是自动引用计数

   自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。

在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码。这在降低程序崩溃,内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。

1.2 内存管理/引用计数

1.2.1概要

OC的内存管理,也就是引用计数。
以办公室的照明管理为例
在这里插入图片描述
在OC中,“对象”相当于办公室的照明设备,在计算机中对象可以有多个被管理。“对象的使用环境”相当于上班进入办公室的人。

上班进入办公室的人对办公室的照明设备发出的动作,在OC的对应关系如下
在这里插入图片描述
使用计数功能计算需要照明的人数,使办公室的照明得到了很好的管理,同样,使用引用计数的功能,对象也就能得到很好的管理,这就是OC的内存管理。
在这里插入图片描述

1.2.2内存管理的思考方式

对引用计数方式内存管理的思考方式:

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

除了上面提到的“生成”,“持有”,“释放”。OC内存管理还有加上“废弃”一词。
对象操作与OC方法的对应
在这里插入图片描述
这些OC有关内存管理的方法,并不包括在该语言。Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。OC内存管理的alloc/retain/release/dealloc方法分别代指NSObject类的alloc类方法,retain实例方法,release实例方法,dealloc实例方法。
在这里插入图片描述
自己生成的对象,自己所持有
使用以下名称开头的方法意味着自己生成的对象只有自己持有

alloc
new
copy
mutableCopyalloc方法

id obj = [[NSObject alloc] init];

new方法

id obj = [NSObject new];

指向生成并持有对象的指针被赋值给变量obj;[[NSObject alloc] init]与[NSObject new]是完全一致的。

copy和mutableCopy这些方法生成的对象,虽然是对象的副本,但和alloc,new方法一样,自己生成并持有对象没有改变。

下列方法也可以
allocMyObject;
newMyObject;
copyThis;
mutableCopyYourObject;

以下方法却不行
allocate;
newer;
copying;
mutableCopyed;

非自己持有的对象,自己也能持有
用alloc/new/copy/mutable Copy意外的方法取得的对象,因为非自己生成并持有,所以不是盖对象的持有者。
在这里插入图片描述
NSMutableArray类对象被赋给变量obj,但变量obj并不持有对象,使用retain方法可以持有对象。
不再需要自己持有的对象时释放
自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法。
在这里插入图片描述

在这里插入图片描述
用alloc/new/copy/mutable Copy方法生成并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必用release方法释放。

如果要用某个方法生成对象,并将其返回给该方法的调用方。
在这里插入图片描述
在这里插入图片描述
原封不动地返回alloc方法生成并持有的对象,就能让调用方也持有该对象。allocObject这个名称符合前文命名规则。
怎么像[NSMutableArray array]方法使取得的对象存在,单自己不持有。
在这里插入图片描述
在这里插入图片描述
也可以通过retain方法将调用autorelease方法取得的对象变为自己持有。

[obj retain];
自己持有对象

上面使用autorelease方法,可以使取得的对象存在,单自己不持有。auto release提供这样的功能,使对象超出指定的生存范围时能够自动并正确的释放。
在这里插入图片描述
根据上文的命名规则这些用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutable Copy命名。

无法释放非自己持有的对象
倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。

1.2.3alloc/retain/release/dealloc实现

(一)alloc
在这里插入图片描述

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

在这里插入图片描述

(二)retaincount

执行alloc后的对象retaincount是“1”;
在这里插入图片描述

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

在这里插入图片描述

(三)retain

在这里插入图片描述

虽然写入了当retained变量超出最大值时发生异常的代码,但实际上只运行了使retained变
量加1的retained++代码。同样地,release实例方法进行retained-并在该引用计数变量为0时
做出处理。下面通过源代码来确认。

(四)release

在这里插入图片描述

同预想的一样,当retained变量大于0时减1,等于0时调用dealloc实例方法,废弃对
以下是废弃对象时所调用的dealloc实例方法的实现。

在这里插入图片描述

具体总结如下:

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

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

1.2.5autorelease

C语言的自动变量,若自动变量超出其作用域,该自动变量将自动废弃。
在这里插入图片描述

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

autorelease 的具体使用方法如下:

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

在这里插入图片描述

NSAutoreleasePool对象的生命周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对像,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
在这里插入图片描述

在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对
NSAutoreleasePool对象进行生成、持有和废弃处理。因此,应用程序开发者不一定非得使用
NSAutoreleasePool对象来进行开发工作。如图1-13所示。
在这里插入图片描述

尽管如此但在大量产生autorelease时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此会产生内存不足的现象。

1.2.6 autorelease实现

autorelease是怎么实现的?
在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法。以下源代
码中,被赋予pool变量的即为正在使用的NSAutoreleasePool对象

1.3 ARC规则

1.3.2内存管理的思考方式

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

1.3.3内存管理的思考方式

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

_strong、_weak、_unsafe_unretained、 _autoreleasing

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

ARC有效

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

ARC无效时

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

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

如“strong”这个名称所示,__strong修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时,随着强引用的失效,引用的对象会随之释放。

附有__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

另外,__strong修饰符同后面要讲的的__weak修饰符和__autoreleasing修饰符一起,可以保证附有这些修饰符的自变量初始化为nil;

id __strong obj;
//以下代码与上相同
id __strong obj = nil;

通过__strong修饰符,不必再次键入retain和release,完美地满足了“引用计数式内存管理的思考方式”;

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

__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修饰符,__weak修饰符河__strong修饰符相反,提供弱引用。弱引用不能持用对象实例。

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

变量obj附加了__weak修饰符,会发生警告;

在这里插入图片描述

变量obj持有对持有对象的弱引用。因此,为了不以自己持有的状态保存自己生成并持有的对象,生成的对象会立即被释放。

如果像下面这样,将对象赋值给附有__storng修饰符的变量之后在赋值给附有__weak修饰符的变量。就不会发生警告;

{
    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被赋值的状态(空弱引用)。如下代码所示:
在这里插入图片描述

在这里插入图片描述

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

__unsafe_unretained修饰符

__unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象

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

该源代码将自己生成并持有的对象赋值给附有__unsafe_unretained修饰符的变量中。虽然使用了unsafe的变量,但编译器并不会忽略,而是给出适当的警告。

warning: assigning retained obj to unsafe_unretained variable;
obj will be released after assignment [-Warc-unsafe-retained-assign]
id __unsafe_unretained obj = [[NSObject alloc] init];

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

__unsafe_unretained修饰符和__weak修饰符t也有差异的地方

id __unsafe_unretained obj1 = nil;
{
	/*
	 * 自己生成并持有对象
	 */
	id __strong_obj0 = [[NSObject alloc] init];
	/*
	 * 因为obj0变量为强引用,
	 * 所以自己持有对象。
	 */
	obj1 = obj0;
	/*
	 * 虽然objo变量赋值给obj1,
	 * 但是obj1变量既不持有对象的强引用也不持有弱引用
	 */
	NSLog(@"A:*@",obj1);
	/*
	 * 输出obj1变量表示的对象
	 */
}
/*
 * 因为objo变量超出其作用域,强引用失效,
 * 所以自动释放自己持有的对象。
 * 因为对象无持有者,所以废弃该对象。
 */
NSLog(@"B:@",obj1);
/*
 * 输出obj1变量表示的对象
 * obj1变量表示的对象
 * 已经被废弃(悬垂指针)!
 * 错误访问!
 */

也就是说,最后一行的NSLog只是碰巧正常运行而已。虽然访问了已经被废弃的对象.

在使用__unsafe_unretained 修饰符时,赋值给附有__strong 修饰符的变量时有必要确保被赋值的对象确实存在。

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

__autoreleasing修饰符

ARC有效时,autorelease修饰符如何?

//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修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。

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

在这里插入图片描述

显式地附加__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,并取得非自己生成并持有的对象。

1.3.4规则

在这里插入图片描述

1. 不能使用retain/release/retainCount/autorelease

内存管理是编译器的工作,因此没有必要使用内存管理的方法(retain/release/retainCount/autorelease)。以下摘自苹果的官方说明。
设置ARC 有效时,无需再次键入retain 或release 代码。”

总之,只能在ARC 无效且手动进行内存管理时使用retain/release/retainCount/autorelease 方法。

  1. 不能使用NSAllocateObject/NSDeallocateObject

一般通过调用NSObject 类的alloc 类方法来生成并持有OC 对象。

id obj = [NSObject alloc];

但是就如GNUstep 的alloc 实现所示,实际上是通过直接调用NSAllocateObject 函数来生成并持有对象的
在ARC 有效时,禁止使用NSAllocateObject 函数。同retain 等方法一样,如果使用便会引起编译错误。

error: 'NSAllocateObject' is unavailable:  
not available in automatic reference counting mode 

同样地,也禁止使用用于释放对象的NSDeallocateObject 函数。

在ARC 无效时,用于对象生成/持有的方法必须遵守以下的命名规则。

  • alloc
  • new
  • copy
  • mutableCopy
    以上述名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。这在ARC 有效时也一样,返回的对象完全没有改变。
  • init
    以init 开始的方法的规则要比alloc/new/copy/mutableCopy 更严格。该方法必须是实例方法,并且必须要返回对象。返回的对象应为id 类型或该方法声明类的对象类型,抑或是该类的超类型或子类型。该返回对象并不注册到autoreleasepool 上。基本上只是对alloc 方法返回值的对象进行初始化处理并返回该对象

4.不显示调用dealloc

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

- (void)dealloc {
	/*  
	 * 此处运行该对象被废弃时  
	 * 必须实现的代码  
	 */  
}

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

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

另外,在ARC 无效时必须像下面这样调用[super dealloc]。

/* ARC 无效 */  
-void)dealloc {
	/* 该对象用的处理 */
	[super dealloc];
}

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

error: ARC forbids explicit message send of 'dealloc'
[super dealloc];  
 ^     ~~~~~~~ 

ARC 会自动对此进行处理,因此不必书写[super dealloc]。dealloc 中只需记述废弃对象时所必需的处理。

5.使用@autoreleasepool块代替NSAutoreleasepool
如__autoreleasepool修饰符项所述,ARC有效时,使用使用@autoreleasepool块代替NSAutoreleasepool。NSAutoreleasePool类不可使用时便会引起编译器报错。

  1. 不能使用区域(NSZone)

虽说ARC 有效时,不能使用区域(NSZone)。正如前所述,不管ARC 是否有效,区域在现在的运行时系统(编译器宏__OBJC2__ 被设定的环境)中已单纯地被忽略。

  1. 对象型变量不能作为C 语言结构体的成员

C 语言的结构体(struct 或union)成员中,如果存在OC 对象型变量,便会引起编译错误。

struct Data {
	NSMutableArray *array;
};
error: ARC forbids Objective-C objs in structs or unions 
NSMutableArray *array; 

C 语言的规约上没有方法来管理结构体成员的生存周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生存周期。例如C 语言的自动变量(局部变量)可使用该变量的作用域管理对象。但是对于C 语言的结构体成员来说,这在标准上就是不可实现的。
要把对象型变量加入到结构体成员中时,可强制转换为void *或是附加前面所述的__unsafe_unretained 修饰符。

struct Data {
	NSMutableArray __unsafe_unretained *array;
};

如前所述,附有__unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。如果管理时不注意赋值对象的所有者,便有可能遭遇内存泄漏或程序崩溃。这点在使用时应多加注意。

8.显式转换id和void*
在ARC无效时,想以下代码这样id变量强制转换void*变量并不会出问题

/* ARC 无效 */  
id obj = [[NSObject alloc] init];
void *p = obj;  
id o = p;
[o release];

但在ARC有效时这边会引起编译错误。

error: implicit conversion of an Objective-C pointer to 'void *' is disallowed with ARC  
void *p = obj;  
		  ^  
error: implicit conversion of a non-Objective-C pointer type 'void *' to 'id' is disallowed with ARC  
id o = p;  
	   ^ 

id型或对象类型变量赋值给void*或者逆向赋值时都需要进行特定的转换,如果只想单纯的赋值,则可以使用“_bridge"转换。

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

像这样,通过“__bridge 转换”,id 和void * 就能够相互转换。
但是转换为void * 的__bridge 转换,其安全性与赋值给__unsafe_unretained 修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。
__bridge 转换中还有另外两种转换,分别是“__bridge_retained 转换”和“ __bridge_transfer转换”。

id obj = [[NSObject alloc] init];  
void *p = (__bridge_retained void *)obj;

__bridge_retained 转换可使要转换赋值的变量也持有所赋值的对象。下面我们来看ARC 无效时的源代码是如何编写的。

/* ARC 无效 */  
id obj = [[NSObject alloc] init];  
void *p = obj;  
[(id)p retain];

__bridge_retained 转换变为了retain。变量obj 和变量p同时持有对象。

__bridge_transfer 转换提供与此相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放

id obj = (__bridge_transfer id)p;

该源代码在ARC无效时又如何表述呢?

/*ARC无效*/
id obj = (id)p;
[obj retain];
[(id)p release];

__bridge_retained 转换与 retain 类似,__bridge_transfer 转换与 release 相似。在给 id obj赋值时 retain 即相当于__strong 修饰符的变量。
如果使用以上两种转换,那么不使用id型或对象型变量也可以生成、持有以及释放对象。虽然可以这样做,但在ARC中并不推荐这种方法。

这些转换多数使用在 Objective-C 对象与Core Foundation 对象之间的相互变换中。

专栏objective-c对象与Core Foundation对象

Core Foundation 对象主要使用在用C语言编写的Core Foundation
框架中,并使用引用计数的对象。在ARC无效时,Core Foundation 框架中的retain/release
分别是CFRetain/CFRelease Core Foundation 对象与Objective-c
对象的区别很小,不同之处只在于是由哪一个框架(Foundation 框架还是 Core Foundation
框架)所生成的。无论是由哪种框架生成的对象,一旦生成之后,便能在不同的框架中使用。Foundation 框架的API 生成并持有的对象可以用
Core Foundation 框架的API释放。当然,反过来也是可以的。 因为Core
Foundation对象与Objective-C对象没有区别,所以在ARC无效时,只用简单的C语言的转换也能实现互换。另外这种转换不需要使用额外的CPU资源,因此也被称为“免费桥”(Toll-Free
Bridge )。

以下函数可用于Objective-C对象与Core Foundation 对象之间的相互变换,即Toll-Free
Bridge 转换。

CFTypeRef CFBridgingRetain(id X) {
	return (__bridge_retained CFTypeRef)X;
}
id CFBridgingRelease(CFTypeRef X)(
	return (__bridge_transfer id)X;
}

1.3.5属性

当ARC 有效时,OC类的属性也会发生变化。

@property (nonatomic, strong) NSString *name;

当ARC 有效时,以下可作为这种属性声明中使用的属性来用。
在这里插入图片描述

以上各种属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。只有copy 属性不是简单的赋值,它赋值的是通过NSCopying 接口的copyWithZone: 方法复制赋值源所生成的对象。
另外,在声明类成员变量时,如果同属性声明中的属性不一致则会引起编译错误。比如下面这种情况。

id obj;

在声明id 型obj 成员变量时,像下面这样,定义其属性声明为weak。

@property (nonatomic, weak) id obj;

编译器出现如下错误。

error: existing ivar 'obj' for __weak property 'obj' must be __weak  
@synthesize obj;  
			^  
note: property declared here  
@property (nonatomic, weak) id obj; 

此时,成员变量的声明中需要附加__weak 修饰符。

id __weak obj;

或者使用strong 属性替代weak 属性。

@property (nonatomic, strong) id obj;

1.3.6数组

1.4ARC的实现

苹果的官方说明中称,ARC 是“由编译器进行内存管理”的,但实际上只有编译器是无法完全胜任的,在此基础上还需要OC运行时库的协助。也就是说,ARC 由以下工具、库来实现。

clang(LLVM编译器)3.0 以上
objc4 Objective-C 运行时库 493.9以上

1.4.1__strong修饰符

赋值给附有__strong修饰符的变量在实际的程序中是怎么实现的?

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

为了便于理解,以后的源代码有时也使用模拟源代码。

/* 编译器的模拟代码 */  
id obj = objc_msgSend(NSObject, @selector(alloc));  
objc_msgSend(obj, @selector(init));  
objc_release(obj); 

2次调用了objc_msgSend方法(alloc方法和init方法),变量作用域结束时通过objc_release释放对象。ARC有效时不能调用release方法,但由此可知,编译器制动插入了release方法。
我们来看看alloc/new/copy/mutableCopy 以外的方法会是什么情况。

{
	id __strong obj = [NSMutableArray array];
}

调用了我们熟知的NSMutableArray 类的array 类方法,得到的结果却与之前稍有不同。

/* 编译器的模拟代码 */  
id obj = objc_msgSend(NSMutableArray, @selector(array)); 
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

虽然最开始的array 方法的调用以及最后变量作用域结束时的release 与之前相同,但中间的objc_retainAutoreleasedReturnValue 函数是什么呢?

objc_retainAutoreleasedReturnValue 函数主要用于最优化程序运行。顾名思义,它是用于自己持有(retain)对象的函数,但它持有的对象应为返回注册在autoreleasepool 中对象的方法,或是函数的返回值。像该源代码这样,在调用alloc/new/copy/mutableCopy 以外的方法,即NSMutableArray 类的array 类方法等调用之后,由编译器插入该函数。

这种objc_retainAutoreleasedReturnValue 函数是成对的, 与之相对的函数是objc_autoreleaseReturnValue。它用于alloc/new/copy/mutableCopy 方法以外的NSMutableArray 类的array 类方法等返回对象的实现上。

下面我们看看NSMutableArray 类的array 类通过编译器会进行怎样的转换。

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

以下为该源代码的转换,转换后的源代码使用了objc_autoreleaseReturnValue 函数。

/* 编译器的模拟代码 */  
+ (id)array {  
	id obj = objc_msgSend(NSMutableArray, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	return objc_autoreleaseReturnValue(obj);
} 

像该源代码这样, 返回注册到autoreleasepool 中对象的方法使用了objc_autoreleaseReturnValue 函数返回注册到autoreleasepool 中的对象。但是objc_autoreleaseReturnValue函数同objc_autorelease 函数不同,一般不仅限于注册对象到autoreleasepool 中。
objc_autoreleaseReturnValue 函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue( )函数,那么就不将返回的对象注册到autoreleasepool 中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue 函数与objc_retain 函数不同,它即便不注册到autoreleasepool中而返回对象, 也能够正确地获取对象。通过objc_autoreleaseReturnValue 函数和objc_retainAutoreleasedReturnValue 函数的协作,可以不将对象注册到autoreleasepool 中而直接传递,这一过程达到了最优化。

在这里插入图片描述

1.4.2__weak修饰符

就像前面的__weak修饰符提供的功能

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

下面我们来看看是如何实现的?

{
	id __weak objobj1 = obj;
}

假设变量obj 附加__strong 修饰符且对象被赋值

/* 编译器的模拟代码 */  
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destroyWeak函数释放该变量。

objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak 函数。

obj1 = 0;
objc_storeWeak(&obj1, obj);

objc_destroyWeak 函数将0 作为参数调用objc_storeWeak 函数。

objc_storeWeak(&obj1, 0); 

即前面的源代码与下列源代码相同。

/* 编译器的模拟代码 */  
id obj1;  
obj1 = 0;  
objc_storeWeak(&obj1, obj); //初始化+赋值 
objc_storeWeak(&obj1, 0); //销毁
  • objc_storeWeak 函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak 修饰符的变量的地址注册到weak 表中。
  • 如果第二参数为0,则把变量的地址从weak 表中删除。

weak 表引用计数表相同,作为散列表被实现。如果使用weak 表,将废弃对象的地址作为键值进行检索,就能高速地获取对应的附有__weak 修饰符的变量的地址。另外,由于一个对象可同时赋值给多个附有__weak 修饰符的变量中,所以对于一个键值,可注册多个变量的地址

释放对象时,废弃谁都不持有的对象的同时,程序的动作是怎样的呢?下面我们来跟踪观察。对象将通过objc_release 函数释放。

objc_release 因为引用计数为0 所以执行dealloc _objc_rootDealloc
object_dispose
objc_destructInstance
objc_clear_deallocating

对象被废弃时最后调用的objc_clear_deallocating 函数的动作如下:

从weak 表中获取废弃对象的地址为键值的记录。
将包含在记录中的所有附有__weak 修饰符变量的地址,赋值为nil。
从weak 表中删除该记录。
从引用计数表中删除废弃对象的地址为键值的记录。

根据以上步骤,前面说的如果附有__weak 修饰符的变量所引用的对象被废弃,则将nil 赋值给该变量这一功能即被实现。由此可知,如果大量使用附有__weak 修饰符的变量,则会消耗相应的CPU 资源良策是只在需要避免循环引用时使用__weak 修饰符
使用__weak 修饰符时,以下源代码会引起编译器警告。

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

因为该源代码将自己生成并持有的对象赋值给附有__weak 修饰符的变量中,所以自己不能持有该对象,这时会被释放并被废弃,因此会引起编译器警告。

warning: assigning retained obj to weak variable; obj will be released after assignment [-Warc-unsafe-retained-assign]
id __weak obj = [[NSObject alloc] init];  
		  ^ 	~~~~~~~~~~~~~~ 

编译器如何处理该源代码呢?

/* 编译器的模拟代码 */  
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&object);

虽然自己生成并持有的对象通过objc_initWeak 函数被赋值给附有__weak 修饰符的变量中,但编译器判断其没有持有者,故该对象立即通过objc_release 函数被释放和废弃。
这样一来,nil 就会被赋值给引用废弃对象的附有__weak 修饰符的变量中。下面通过NSLog 函数来验证一下。

{
	id __weak obj = [[NSObject alloc] init];
	NSLog(@"obj = %@", obj);
}

输出

obj = (null)

立即释放对象

如前所述,以下源代码会引起编译器警告。

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

这是由于编译器判断生成并持有的对象不能继续持有。附有__unsafe_unretained 修饰符的变量又如何呢?

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

与__weak 修饰符完全相同,编译器判断生成并持有的对象不能继续持有,从而发出警告。
该源代码通过编译器转换为以下形式。

/* 编译器的模拟代码 */  
id obj = objc_msgSend(NSObject, @selector(alloc)); 
objc_msgSend(obj, @selector(init));  
objc_release(obj); 

objc_release 函数立即释放了生成并持有的对象,这样该对象的悬垂指针被赋值给变量obj 中。
那么如果最初不赋值变量又会如何呢?下面的源代码在ARC 无效时必定会发生内存泄漏。

[[NSObject alloc] init];

由于源代码不使用返回值的对象,所以编译器发出警告。
可像下面这样通过向void 型转换来避免发生警告。

(void)[[NSObject alloc] init];

不管是否转换为void,该源代码都会转换为以下形式

/* 编译器的模拟代码 */  
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);

虽然没有指定赋值变量,但与赋值给附有__unsafe_unretained 修饰符变量的源代码完全相同。由于不能继续持有生成并持有的对象,所以编译器生成了立即调用objc_release 函数的源代码而由于ARC 的处理,这样的源代码也不会造成内存泄漏。

另外,能调用被立即释放的对象的实例方法吗?

(void)[[[NSObject alloc] init] hash];

该源代码可变为如下形式:

/* 编译器的模拟代码 */  
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);

在调用了生成并持有对象的实例方法后,该对象被释放。看来“由编译器进行内存管理”这句话应该是正确的。

这次我们再用附有__weak 修饰符的变量来确认另一功能:**使用附有__weak 修饰符的变量,即是使用注册到autoreleasepool 中的对象。**

{
	id __weak objobj1 = obj;
	NSLog(@"%@", obj1);
}

该源代码可转换为如下形式:

/* 编译器的模拟代码 */  
id obj1;  
objc_initWeak(&obj1, obj);  
id tmp = objc_loadWeakRetained(&obj1);  
objc_autorelease(tmp);  
NSLog(@"%@", tmp);  
objc_destroyWeak(&obj1); 

与被赋值时相比,在使用附有__weak 修饰符变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease 函数的调用。这些函数的动作如下。

  • objc_loadWeakRetained 函数取出附有__weak 修饰符变量所引用的对象并retain。
  • objc_autorelease 函数将对象注册到autoreleasepool 中。

由此可知,因为附有__weak 修饰符变量所引用的对象像这样被注册到autoreleasepool 中,所以在@autoreleasepool 块结束之前都可以放心使用。但是,如果大量地使用附有__weak 修饰符的变量,注册到autoreleasepool 的对象也会大量地增加,因此在使用附有__weak 修饰符的变量时,最好先暂时赋值给附有__strong 修饰符的变量后再使用

1.4.3__autoreleaseing修饰符

将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。

@autoreleasepool {  
	id __autoreleasing obj = [[NSObject alloc] init];  
}

该源代码主要将NSObject 类对象注册到autoreleasepool 中,可作如下变换:

/* 编译器的模拟代码 */  
id pool = objc_autoreleasePoolPush();  
id obj = objc_msgSend(NSObject, @selector(alloc));  
objc_msgSend(obj, @selector(init));  
objc_autorelease(obj);  
objc_autoreleasePoolPop(pool); 

这与苹果的autorelease 实现中的说明完全相同。虽然ARC 有效和无效时,其在源代码上的表现有所不同,但autorelease 的功能完全一样。
alloc/new/copy/mutableCopy 方法群之外的方法中使用注册到autoreleasepool 中的对象会如何呢?下面我们来看看NSMutableArray 类的array 类方法。

@autoreleasepool {
	id __autoreleasing obj = [NSMutableArray array];
}

这与前面的源代码有何不同呢?

/* 编译器的模拟代码 */  
id pool = objc_autoreleasePoolPush();  
id obj = objc_msgSend(NSMutableArray, @selector(array));  
objc_retainAutoreleasedReturnValue(obj);  
objc_autorelease(obj);  
objc_autoreleasePoolPop(pool); 

虽然持有对象的方法从alloc 方法变为objc_retainAutoreleasedReturnValue 函数, 但注册autoreleasepool 的方法没有改变,仍是objc_autorelease 函数。

1.4.4引用计数

这里提供获取引用计数数值的函数

uintptr_t _objc_rootRetainCount(id obj)

如上声明的_objc_rootRetainCount 函数可获取指定对象的引用计数数值。请看以下几个例子。

id __strong obj = [[NSObject alloc] init];
NSLog(@"retain count =8d", _objc_rootRetainCount(obj));

该源代码中,对象仅通过变量obi的强引用被持有,所以为1

下面使用__weak 修饰符。

{
	id __strong obj = [[NSObject alloc] init];
	id __weak o = obj;
	NSLog(@"retain count =%d", _objc_rootRetainCount(obj));
}

由于弱引用并不持有对象,所以赋值给附有__weak修饰符的变量中也必定不会改变引用计数数值。

retain count = 1

结果同预想一样。那么通过__autoreleasing修饰符向autoreleasepool注册又会如何呢?

@autoreleasepool {
	id __strong obj = [NSObject alloc] init];
	id __autoreleasing o = obj:
	NSLog(@"retain count *d", objc_rootRetainCount(obj));
}

结果如下:

retain count = 2

对象被附有__strong修饰符变量的强引用所持有,且被注册到autoreleasepool中,所以为2。以下确认@autoreleasepool 块结束时释放已注册的对象。

{
	id __strong obj = [[NSObject alloc] init];
	@autoreleasepool {
		id __autoreleasing o = obj;
		NSLog(@"retain count = %d in @autoreleasepool", _objc_rootRetainCount(obj));
	}
	NSLog(@"retain count = %d", _objc_rootRetainCount(obj));
}

在@autoreleasepool 块之后也显示引用计数数值。

retain count = 2 in @autoreleasepool
retain count = 1

如我们预期的一样,对象被释放

以下在通过附有__weak修饰符的变量使用对象时,基于显示autoreleasepool 状态的_objc_autoreleasePoolPrint函数来观察注册到autoreleasepool中的引用对象。

@autoreleasepool {
	id __strong obj = [[NSObject alloc] init];
	_objc_autoreleasePoolPrint();
	id __weak o = obj;
	NSLog(@"before usingweak: retain count = %d", _objc_rootRetainCount(obj));
	NSLog(@"class = %@", [o class]);
	NSLog(@"after using__weak: retain count = %d", _objc_rootRetainCount (obj));
	_objc_autoreleasePoolPrint();
}

在这里插入图片描述

通过上面可以看出,不使用__autoreleaseing修饰符,仅仅使用附有__weak声明的变量也能将引用对象注册到autoreleasepool中

虽然以上这些例子均使用了_objc_rootRetainCount 函数,但实际上并不能够完全信任该函数取得的数值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值