【Objective -- C】—— 自引用计数

news/2024/7/20 13:50:38 标签: 开发语言, ios, 内存管理

【Objective -- C】—— 自引用计数

  • 一. 内存管理/自引用计数
    • 1.自引用计数
    • 2.内存管理的思考方式
      • 自己生成的对象,自己持有
      • 非自己生成的对象,自己也能持有
      • 不再需要自己持有的对象时释放
      • 无法释放非自己持有的对象
    • 3.alloc/retain/release/dealloc实现
    • 4. autorelease
    • 5.autorelease实现
  • 二. ARC规则
    • 1. 所有权修饰符
      • __strong修饰符
      • __weak修饰符
      • __unsafe_unretained 修饰符
      • __autoreleasing 修饰符
    • 2.规则
      • 不能使用retain/release/retainCount/autorelease
      • 不要显式调用dealloc
      • 使用@autoreleasepool块替代NSAutoreleasePool
      • 显式转换id 和void *
    • 3. 属性
    • 4.数组

一. 内存管理/自引用计数

1.自引用计数

顾名思义,自动引用计数 (ARC, Automatic Reference Counting )是指内存管理中对引用采 取自动计数的技术。当一个对象的引用计数为大于0的计数,表示这个对象被持有不能被释放,当引用计数为0时表示这个对象需要被释放掉。

引用计数的原理:引用计数可以有效的管理对象的生命周期,当我们创建一个新对象的时候,它(该对象所在的内存块)的引用计数为1,当有一个新的指针指向这个对象时,我们将其引用计数加1,当某个指针不在指向这个地址时,我们将其应用计数减1,当对象的引用计数变为0时,说明这块内存不在被任何指针指向,这个时候系统就会将对象销毁,回收内存。从而达到管理内存的目的。

举例说明:
假设办公室里的照明设备只有一个。上班进入办公室的人需要照明,所以要把灯打开。而对 于下班离开办公室的人来说,己经不需要照明了,所以要把灯关掉。办公室的照明设备相当于该对象,办公室中的人数相当于该对象的引用计数器,来一个人办公室的引用计数器加一,走一个人引用计数器就减一,只要办公室有人这个灯就得开,没人的时候就可以关掉,相当于释放这个对象。

对照明设备所做的动作对 Objective- C对象所做的动作
开灯生成对象
需要照明持有对象
不需要照明释放对象
关灯废弃对象

2.内存管理的思考方式

对象操作Objective-C 方法
生成并持有对象alloc/new/copy/mutableCopy 等方法
持有对象retain方法
释放对象release 方法
废弃对象dealloc方法

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

  • alloc
  • new
  • сору
  • mutableCopy
    例如:
id obj = [[NSObject alloc] init];
id obj = [NSObject new];

其中 [[NSObject alloc] init] 和 [NSObject new] 是完全一致的。
另外,根据上述“使用以下名称开头的方法名”, 下列名称也意味着自己生成并持有对象。

  • allocMyObject
  • newThatObject
  • copyThis
  • mutableCopyYourObject

但是对于以下名称,即使用alloc/new/copy/mutableCopy名称开头,并不属于同一类别方法。

  • allocate
  • newer
  • copying
  • mutableCopyed

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

用上述项目之外的方法取得的对象,即用alloc/new/copy/mutableCopy 以外的方法取得的对象,因为 非自己生成并持有, 所以自己不是该对象的持有者。

//取得非自己生成并持有的对象

id obj = [NSMutableArray array]:

//取得的对象存在,但自己不持有对象

[obj retain];

//自己持有对象

从上述的举例可以看出,通过retain方法,非自己持有生成的对象跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成了自己所持有的。

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

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

//自己生成并持有对象
id obj = [[NSObject alloc] init];

[obj releasel];

//释放对象, 指向对象的指针仍然被保留在变量obj 中,貌似能够访问, 但对象一经释放绝对不可访问。

如此,用alloc 方法由自己生成并持有的对象就通过 ** release ** 方法释放了。
自己生成而非自己 所持有的对象,若用retain 方法变为自己持有,也同样可以用release 方法释放。

//取得的对象存在,但自己不持有对象。
id obj = [NSMutableArray array];

// 自己持有对象。
[obj retain];

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

用alloc/new/copy/mutableCopy 方法生成并持有的对象,或者用retain方法持有的对象, 一旦 不再需要,务必要用release 方法进行释放。

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

- (id) allocObject {
	// 自己生成并持有对象
	id obj = [[NSObject alloc] init];
	
	return obj;
}

//调用上边的方法,也可以取得非自己生成并持有的对象
id obj1 = [obj0 allocObject];

使用某个方法取得对象,但是不持有对象:

- (id)object {
	//自己持有对象
	id obj - [[NsObject alloc] init]; 
	
	//取得的对象存在,但自己不持有对象
	[obj autoreleasel];
	
	return obj;
}

上例中,我们使用了autorelease 方法。用该方法,可以使取得的对象存在,但自己不持有对象。autorelease 提供这样的功能,使对象在超出指定的生存范围时能够自动并正确地释放 (调用release方法 )。

但是也可以通过retain方法将调用autoreleasel 方法取得的对象变为自己持有。
如下代码所示:

//取得的对象存在,但自己不持有对象
id obj1 = [obj0 object];

//retain方法将调用autorelease 方法取得的对象变为自己持有。
//自己持有对象
[obj1 retain];

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

对于用alloc/new/copy/mutableCopy 方法生成并持有的对象,或是用retain 方法持有的对象, 由于持有者是自己,所以在不需要该对象时需要将其释放。而由此以外所得到的对象绝对不能释放。倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。例如自己生成并持有对象后, 在释放完不再需要的对象之后再次释放。

// 自己生成并持有对象
id obj = [[NsObject allocl] init];

//对象己释放
[obj release];
//释放之后再次释放已非自己持有的对象,应用程序崩溃

[obj release];
//崩溃情况:
//再度废弃已经废弃了的对象时崩溃,访问已经废弃的对象时崩溃

或者在“取得的对象存在,但自己不持有对象”时释放。

- (id)object {
    //自己持有对象
    id obj = [[NSObject alloc] init];

    //取得的对象存在,但自己并不持有
    [obj autorelease];
    
    return obj;
}

//调用上边的方法,可以取得的对象存在,但是并不持有
id obj1 = [obj0 object];

[obj1 release];
//释放了非自己持有的对象!
//这肯定会导致应用程序崩溃!

3.alloc/retain/release/dealloc实现

GNUstep是Cocoa框架的互换框架,两者的行为和实现方式是一样的,首先来看一下GNUstep源代码中的NSObjection类的alloc类方法。

id obj = [NSObject alloc];

上述调用NSObject类的alloc类方法在NSObject.m源代码中的实现如下:

+ (id) alloc {
    return [self allocWithZone:NSDefaultMallocZone()];
    
}

+ (id) allocWithZone: (NSZone*) z {
    return NSAllocateObject (self, 0, z);
}

通过 allocWithZone: 类方法调用NSAllocateObject函数分配了对象。下面我们来看NSAllocateObject函数。

struct obj_layout {
	NSUInteger retained;
};
inline id
NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone){
	int size= 计算容纳对象所需内存大小 ;
	id new = NSZoneMalloc (zone, size) ;
	memset (new, 0, size) ;
	new = (id)&((struct obj_layout *) new) [1];
}

NSAllocateObject函数通过调用 NSZoneMalloc函数来分配存放对象所需的内存空间,之后将该内存空间置0, 最后返回作为对象而使用的指针。

以下是去掉NSZone后的简化源代码。

struct obj_layout {
	NSUInteger retained;
};

+(id) alloc {
	intsize=sizeof (structobj_layout) +对象大小;
	structobj_layout*p=(structobj_ layout*)calloc(1,size);
	return(id)(p+1);
}

alloc类方法用struct obj_layout 中的retained整数来保存引用计数, 并将其写入对象内存头部,
该对象内存块全部置0后返回。 以下用图示来展示有关GNUstep的实现, alloc类方法返回的对象。
在这里插入图片描述
对象的引用计数可通过retainCount实例方法获得

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

执行alloc后对象的retainCount是“1”。下面通过GNUstep的源代码来确认。

- (NSInteger) retainCount {
    return NSExtraRefefCount (self) + 1;
}
inline NSUInteger
NSExtraRefefCount (id anObject){
    return ((struct obj_layout* ) anObject)[-1].retained;
}

由对象寻找到地址内存的头部,从而访问其中的retained变量。
因为分配时全部置0,所以 retained为0。由NSExtraRefCount(self)+1得出,retainCount为1。可以推测出,retian方法使retained变量加1,而release使retained变量减1。

[obj retain];

下面来看一下怎样调出retain实例方法。

-(id)retain {
NSIncrementExtraRefCount( self);
return self;
}

inline void
NSIncrementExtraRefCount( id anObject)
{
if (((struct obj_layout *) anObject)[-1].retained == UINT_MAX-1)
	[NSException raise: NSInternalInconsistencyException format: @"NSIncrementExtraRefCount () asked to increment too far");

	((structobj_layout*) an0bject)[-1].retained++;
}

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

[obj release];

下面是release实例方法的实现:

-(void)release {
	if (NSDecrementExtraRefCountWasZero(self))
	[self dealloc];
}

BOOL
NSDecrementExtraRefCountWasZero (id anObject) {
	if (((struct obj_layout *)anObject)[-1].retained==0) (
		return YEs;
	} else {
		((struct obj_layout*)anObject) [-1].retained--;
		return No;
	}
}

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

 - (void) dealloc {
	NSDeallocateObject(self);
}
inline void
NSDeallocateObject (id an0bject) {
	struct obj_layout *o=& ((struct obj_layout*) anObject)[-1];
	free(o);
}

上述代码仅废弃由alloc分配的内存块。
以上就是alloc/retain/release/dealloc在 GNUstep中的实现。具体总结如下:

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

4. autorelease

顾名思义,autorelease就是自动释放,类似于C语言中的自动变量的特性。程序执行的时候,若某自动变量超出其作用域,该自动变量将被废弃。

{
	int a;
}
//超出变量的作用域,自动废弃,不可访问。

autorelease 会像 C 语言的自动变量那样来对待对象实例。当超出其作用域(相当于交操作用
域)时,对象实例的 release 实例方法被调用。另外,同 C 语言的自动受量不同的是,编疼人员
可以设定变量的作用域。
autorelease 的具体使用方法如下:
(1)生成并持有 NSAutoreleasePool 对象:
(2)调用已分配对象的 autorelease 实例方法;
(3) 废弃 NSAutoreleasePool 对象。

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

	NSAutoreleasePool pool =[[ NSAutoreleasePool alloc ] init ];
	id obj =[[ NSObject alloc ] init ];
	[ obj autorelease ];
	[ pool drain ];

上述源代码中最后一行的"[ pool drain ]“等同于”[ obj release ]"。
但是,在产生大量autorelease的对象的时候,只要不放废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。典型的例子是读入大量图像的同时改变其尺寸。图像文件读入到 NSData 对象,并从中生成 UIImage 对象,改变该对象尺寸后生成新的 UIImage 对象。这种情况下,就会大量产生 autorelease 的对象。

for ( int i =0; i <图像数;++ i ){
	/*
	*读入图像
	*大量产生 autorelease 的对象。
	*由于没有废弃 NSAutoreleasePool 对象
	*最终导致内存不足!
	*/
}

在此情况下,有必要在合适的地方生成,持有或者废弃 NSAutoreleasePool 对象。

for ( int i =0; i <图像数;++ i ){
	 NSAutoreleasePool * pool =[[ NSAutoreleasePool alloc ] init ];
	/*读入图像
	 *大量产生 autorelease 的对象。
	 */
	[ pool drain ];
	/*通过[ pool drain ],
	 * autorelease 的对象被一起 release 。
	 */
 }

另外,Cocoa框架🀄️也有很多类方法用于返回autorelease的对象,比如NSMutableArray类的arrayWithCapacity类方法。

id array = [NSMutableArray arrayWithCapacity:1];

此代码等同于以下代码。

id array1 = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

5.autorelease实现

[obj autorelease];

autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。
如下面源代码所示:

- (id) autorelease {
    [NSAutoreleasePool addObject:self];
}
+void)addObject {
    NSAutoreleasePool*  pool = 取得正在使用的NSAutoreleasePool对象。
    if (pool != nil) {
        [pool addObject];
    } else {
        NSLog (@"NSAutoreleasePool对象非存在状态下调用autorelease");
    }
}

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

	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
  [obj autorelease];

如果嵌套生成或者持有的NSAutoreleasePool对象,理所当然会使用最内层的对象。

	NSAutoreleasePool *pool0=[[ NSAutoreleasePool alloc ] init ]; 
		NSAutoreleasePool * pooll =[[ NSAutoreleasePool alloc ] init ];
			NSAutoreleasePool *pool2=[[ NSAutoreleasePool alloc ] init ]; 
			id obj =[[ NSObject alloc ] init ];
			[ obj autorelease ];
		  [pool2 drain ];
	   [ pooll drain ];
	[pool0 drain ];

下面来看一下addObject实例方法的实现

- (void) addObject: (id)anObj {
	[array addObject:anobject];
}

如果调用NSObject类中的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组里。

[pool drain];

以下为通过drain实例方法废弃正在使用的NSAutoreleasePool对象。

- ( void ) drain 
	[ self dealloc ];
}
- ( void ) dealloc 
	[ self emptyPool ];
	[ array release ];
}
- ( void ) emptyPool 
	 for ( id obj in array ){
	[ obj release ];
}

虽然调用了好几个方法,但可以确定对于数组中的所有对象都调用了 release 实例方法。

二. ARC规则

1. 所有权修饰符

Objective-C变成中为了处理对象,可将变量类型定义为id类型,或者各种对象类型。
所谓的对象类型就是指向NSObject这样的Objective-C类的指针,例如“NSObject*”。id类型用于隐藏对象类型的类名部分相当于“void*”。
ARC有效的时候,id类型和对象类型同C语言其他类型不同。必须加上所有权修饰符。所有权修饰符一共有四种:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autorreleasing修饰符

__strong修饰符

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

id obj = [[NSObject alloc] init];
//等价于
id __strong obj = [[NSObject alloc] init];

因此在ARC无效的时候,表达的形式如下:

//ARC无效时
id obj = [[NSObject alloc] init];
[obj release];

为了释放生成并持有的对象,增加了调用release方法的代码。
__strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域时会被废弃,随着强引用的时效,引用对象会随之释放。如下面代码所示:

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

在取得非自己生成并持有的对象时,
通过强引用持有一个对象意味:这个对象的引用计数+1,仅限于取得非自己生成的对象。

{	
	//取得非自己生成并持有对象
	id __strong obj = [NSMutableArray array];
	
	//因为变量为强引用,所以自己持有对象:
	//**通过强引用持有一个对象意味着这个对象的引用计数加一**
	
}
	//因为变量obj超出其作用域,强引用失效。
	//所以自动释放自己持有的对象。

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

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

因此不难发现,__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确的管理器对象的所有者。

即使是Objective-C的成员变量,也可以在方法参数上,使用赋有__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
{
	//test 持有rest 对象的强引用
	id __strong test = [[Test alloc] init];
	//Test 对象的obj_成员,
	//持有NsObject 对象的强引用。
	[test setObject: [[NSObject alloc] init]];
}
	//因为test 变量超出其作用域,强引用失效, 
	//所以自动释放rest 对象。
	//Test 对象的所有者不存在,因此废弃该对象。
	
	//废弃rest 对象的同时,
	//Test 对象的obj_ 成员也被废弃, Nsobject 对象的强引用失效,
	//目动样放NSobject 对象。
	//NSObject 对象的所有者不存在,因此废弃该对象。

无需额外工作便可以使用于类成员变量以及方法参数中。

另外,_strong修饰符同后面要讲的_w eak 修饰符和_autoreleasing修饰待一起,可以保 证将附有这些修饰符的自动变量初始化为nil 。

正如苹果宣称的那样,通过_ strong修饰符,不必再次键入retain 或者release,完美地满足 了“ 引用计数式内存管理的思考方式” :

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

前两项“ 自己生成的对象,自己持有” 和“ 非自己生成的对象,自己也能持有” 只需通过对 带 strong 修饰符的变量赋值便可达成 。通过废弃带_ strong修饰符的变量 (变量作用域结束或 是成员变量所属对象废弃)或者对变量赋值,都可以做到“ 不再需要自己持有的对象时释放”。 最后一项“ 非自己持有的对象无法释放”,由于不必再次键入release,所以原本就不会执行。这 些都满足于引用计数式内存管理的思考方式。

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

__weak修饰符

在自引用计数中,有一个重大的问题就是“循环引用”的问题。循环引用容易生内存泄漏的问题,(内存泄漏:应当废弃的对象在超出其生存周期后,继续存在。),循环引用形成内存泄漏的原因:对象之间相互引用,使本身的引用计数无法归零

使用 weak 修饰符可以避免循环引用。
__weak 修饰符与 __strong 修饰符相反,提供弱引用。弱引用不能持有持有对象实例。

先来看下面这段代码:

id __weak obj= IINsobject allocl init];

变量obi 上附加了_weak修饰符,编译器会发出警告。
(NSObject实例被分配内存并且有一个强引用,将该对象赋值给弱引用时,不会导致对象的引用计数增加,对象可能在符之后立即被释放掉)。

将自己生成并持有的对象赋值给附有_ weak修饰符的变量obj。即变量w持有对持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象 会立即被释放。 编译器对此会给出警告。如果将对象赋值给附有strong 修饰符的 变量之后再赋值给附有_ weak 修饰符的变量,就不会发生警告了。

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

__ weak 修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。

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

id __weak obj1 = nil;
{	
	//自己生成并持有对象
	id __strong obj0 = [[NSObject alloc] init];
	//因为obj0变量为强引用, 所以自己持有对象
	obj1 = obj0;
	//obj1变量持有对象的弱引用
	NSLog (@"A:%@" , obj1);
	//输obj1变量持有的弱引用的对象
}
//因为obj0变量超出其作用域,强引用失效, 
//所以自动释放自己持有的对象。 
//因为对象无持有者,所以废弃该对象。
//废1弃对象的同时,
//持有该对象弱引用的obj1变量的弱引用失效,ni1赋值给obj1。

NSLog (@"B:%@" , obj1);
//输出赋值给obj1变量中的nil

代码的执行结果如下:
A: <NSObject: 0x753180>
B: (null)

__unsafe_unretained 修饰符

附有_ unsafe unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并 持有的对象不能继续为自己所有,所以生成的对象会立即被释放。 但是,两者之间还是有差异的:

id _unsafe_unretained obj1 = nil;
{
	//自己生成并持有对象
	id __strong obj0  = [[NSObject alloc] init]; 
	//因为obj0变量为强引用,
	//所以自己持有对象。
	obj1 = obj0;
	//虽然obj0变量赋值给obj1,
    //但是obj 1变量既不持有对象的强引用也不持有弱引用
	NSLog (@"A: %@ ", obj1);
	//输出obj1变量表示的对象

}
//因为obj0变量超出其作用域,强引1用失效,
//所以自动释放自己持有的对象。
//因为对象无持有者,所以废弃该对象。
NSLog (@"B: %@ ", obj1);

//输出obj1变量表示的对象
//obj1变量表示的对象
//已经被废弃(悬垂指针)! 错误访问!

执行结果如下:
A: <NSObject: 0x753180>
B: <NSObject : 0x753180>

最后一行的NSLog 只是碰巧正常运行而己。虽然访问了已经被废弃的对象,但应用程序在个别运行状况下才会崩溃。

__autoreleasing 修饰符

指定“@autoreleasepool 块”来替代“NSAutoreleasePool 类对象生成、持有以及废弃”这 一范围。
在ARC 有效时,用@autoreleasepol 块替代NSAutoreleasePool 类, 用附有autoreleasing修饰符的变量替代autorelease 方法。如下图所示:
在这里插入图片描述
取得非自己生成并持有的对象时,该对象已被注册到了autoreleasepool。这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy 开始,如果不是则自动将返回值的对象注册到autoreleasepool。 另外,init 方法返回值的对象不注册到autoreleas epool 。

@autoreleasepool {
	//取得非自己生成并持有的对象
	id __strong obj = [NSMutableArray array];
	//因为变量obj 为强引用,所以自己持有对象。
	//并且该对象
	//由编译器判断其方法名后
	//自动注册到autoreleasepool
}

不使用_ autoreleasing修饰符也能使对象注册到autoreleasepool。

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

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

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

2.规则

在ARC 有效的情况下编评源代码,必须遊守一定的规则。下面就是具体的ARC的规则。
• 不能使用retain/release/retainCount/autorelease
• 不能使用NSAllocateObject/NSDeallocateObject
• 须遵守内存管理的方法命名规则
• 不要显式调用dealloc
• 使用@autoreleasepool 块替代NSAutoreleasePool
• 不能使用区域 ( NSZone )
• 对象型变量不能作为C语言结构体(structunion)的成员
• 显式转换“id” 和“void*”

不能使用retain/release/retainCount/autorelease

设置ARC有效时,无需再次键入retain或release代码。
retainCount 和release 都会引起编译错误,因此不能使用以下代码。

for (;) {
	NSUInteger count = (obj retainCount];
	[obj release]; 
	if(count==1)
		break:
}

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

不要显式调用dealloc

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

- (void) dealloc {
	free (buffer_);
 }

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

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

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

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

使用@autoreleasepool块替代NSAutoreleasePool

ARC有效时,使用@autoreleasepool快替 1 NSAutoreleasePool。

显式转换id 和void *

id型或对象型变量赋值给void*或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋 值,则可以使用 “_ bridge 转换”,如下代码所示:

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

像这样,通过“_ bridge转换”,id和void*就能够相互转换。
如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

___bridge转换中还有另外两种转换,分别是“__bridge_retained转换”和“ __bridge_stransfer 转换”。

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

__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象。__bridge_retained 转换变为了retain。变量obj 和变量p 同时持有对象。

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

id obj = (id)p; 
[obj retain]; 
[(id) p release];

__bridge_retained转换与retain类似,_bridge transfer转换与release相似。在给id obj赋值时retain 即相当于strong修饰符的变量。

3. 属性

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

@property (nonatomic, strong) Nsstring *name;

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

属性声明的属性所有权修饰符
assign_unsafe_unretained 修饰符
copy_strong修饰符(但是赋值的是被复制的对象〕
retain_strong修饰符
strong_strong修饰符
unsafe_unretained_unsafe_unretained 修饰符
weak_weak修饰符

以上各种属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。
另外,在声明类成员变量时,如果同属性声明中的属性不 一致则会引起编译错误。

4.数组

__unsafe unretained修饰符以外的__strong/__weak/__autoreleasing 修饰符保证其指定的变量 初始化为nil。
先看一下这个例子:

{
	id objs[2];
	objs[0] = [[NSObject allocl] init]; 
	objs[1]= [NSMutableArrayarray];
}

数组超出其变量作用域时,数组中各个附有_ strong修饰符的变量也随之失效,其强引用消失,所赋值的对象也随之释放。但在C语言的动态数组中也可以使用附有__strong 修饰符 的变量,只是必须要遵守一些事项。以下按顺序说明。
声明动态数组用指针。

id __strong *array=nil;

其次,使用calloc 两数确保想分配的附有_ strong 修饰符变量的容量占有的内存块。

array = (id strong* )calloc(entries, sizeof(id));

该源代码分配 了entries 个所需的内存块。不使用calloc 两 数,在用malloc 两数分配内存后可用memset 等西数将内存填充为0。

像这样,通过calloe 两数分配的动态数组就能完全像静态数组一样使用。

但是,如果只是简单地用 free 函数废弃了数组用内存块的情况 下, 数组各元素所 赋值的对象不能再次释放,从而引起内存泄漏。

free (array);

因为在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在 动态数组中,编译器不能确定数组的生存周期,所以无从处理。


http://www.niftyadmin.cn/n/5416999.html

相关文章

【C++】list模拟实现list迭代器失效问题

list模拟实现&list迭代器失效问题 一&#xff0c;list模拟实现1. list的主要框架接口模拟2. list构造&拷贝构造&析构3. list迭代器3.1 普通迭代器3.2 const迭代器 4. 增删查改 二&#xff0c;迭代器失效问题1. list的迭代器失效原因2. 解决办法 一&#xff0c;list…

Compose UI 之 MediumLarge TopAppBar

Medium&Large TopAppBar 前面文章介绍了 Small 类型的 TopAppBar&#xff1a;TopAppBar CenterAlignedTopAppBar 。下来介绍 Medium 和 Large 类型的 TopAppBar&#xff1a;MediumTopAppBar LargeTopAppBar 。 MediumTopAppBar 上面介绍了Small 类型的 TopAppBar (TopAp…

java网络编程 01 IP,端口,域名,TCP/UDP, InetAddress

01.IP 要想让网络中的计算机能够互相通信&#xff0c;必须为计算机指定一个标识号&#xff0c;通过这个标识号来指定要接受数据的计算机和识别发送的计算机&#xff0c;而IP地址就是这个标识号&#xff0c;也就是设备的标识。 ip地址组成&#xff1a; ip地址分类&#xff1a;…

ky10 server 银河麒麟服务器主备搭建 (nginx+keepalived)

下载脚本代码 git clone https://gitcode.net/zengliguang/nginx_keepalived_ky10_x.git 进入脚本路径 更新脚本代码 更新完成 执行安装脚本 安装nginx离线编译安装依赖 解压nginx源码 检查环境 编译 nginx安装成功 安装keepalived keepalived安装成功

C语言——函数指针——函数指针变量详解

函数指针变量 函数指针变量的作用 函数指针变量是指向函数的指针&#xff0c;它可以用来存储函数的地址&#xff0c;并且可以通过该指针调用相应的函数。函数指针变量的作用主要有以下几个方面&#xff1a; 回调函数&#xff1a;函数指针变量可以作为参数传递给其他函数&…

.Net Core/.net 6/.Net 8 实现Mqtt客户端

.Net Core/.net 6/.Net 8 实现Mqtt客户端 客户端代码调用 直接上代码 nuget引用 MQTTnet 客户端代码 using MQTTnet; using MQTTnet.Client; using MQTTnet.Packets; using System.Text;namespace Code.Mqtt {/// <summary>/// Mqtt客户端/// </summary>public cla…

Rust错误处理和Result枚举类异常错误传递

Rust 有一套独特的处理异常情况的机制&#xff0c;它并不像其它语言中的 try 机制那样简单。 首先&#xff0c;程序中一般会出现两种错误&#xff1a;可恢复错误和不可恢复错误。 可恢复错误的典型案例是文件访问错误&#xff0c;如果访问一个文件失败&#xff0c;有可能是因…

线程有几种状态,状态之间的流转是怎样的?

Java中线程的状态分为6种&#xff1a; 1.初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 2.运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;READY&#xff09;和运行中&#xff08;RUNNING&#xff09;两种状态笼统的称为“运行”…