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

【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);

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/444230.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣--滑动窗口438.找到字符串中所有字母异位词

思路分析&#xff1a; 使用两个数组snum和pnum分别记录字符串s和p中各字符出现的次数。遍历字符串p&#xff0c;统计其中各字符的出现次数&#xff0c;存储在pnum数组中。初始化snum数组&#xff0c;统计s的前m-1个字符的出现次数。从第m个字符开始遍历s&#xff0c;通过滑动窗…

《YOLO5Face: Why Reinventing a Face Detector》为什么要重塑人脸检测器论文阅读

正好周末的时间天气也不错出去走走精神不错&#xff0c;回来读一篇论文这个论文之前查资料的时候看到的但是没有完整看下&#xff0c;今天正好花点时间整体看一下&#xff0c;下面是我自己阅读过程中使用翻译软件结合自己理解的阅读记录&#xff0c;感兴趣的话可以看下&#xf…

知识图谱 | 2023年图书馆学、情报学CSSCI期刊论文主题透视

数据来源 检索平台来源期刊年份有效数据中国知网大学图书馆学报国家图书馆学刊情报科学情报理论与实践情报学报情报杂志情报资料工作数据分析与知识发现图书馆建设图书馆论坛图书馆学研究图书馆杂志图书情报工作图书情报知识图书与情报现代情报信息资源管理学报中国图书馆学报2…

性能测试高阶内容:了解TPS和RT之间关系

引言 在开始今天的内容讲解之前&#xff0c;我们应该回顾一下&#xff0c;在我的全链路压测专栏中的第一篇&#xff0c;我就已经介绍了当前的性能测试在互联网企业中的重要性&#xff0c;已经性能在互联网行业中的占比是多少。 这个时候是不是会有同学问我&#xff0c; 你已经…

JVM-1

目录 1.基础知识 1.栈 2.本地方法栈 3.程序计数器 4.堆 5.方法区 6.JVM内存可见性 2.虚拟机类加载机制 1.加载 2.验证 3.准备 4.解析 5.初始化 6.使用 7.卸载 1.基础知识 JVM内存模型&#xff08;5种&#xff09;&#xff1a;栈&#xff0c;本地方法栈&#xff…

Java项目:44 ssm003在线医疗服务系统+jsp(含文档)

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 主要功能 前台登录&#xff1a; 注册用户&#xff1a;用户名、密码、姓名、联系电话 注册医生&#xff1a;医生工号、密码、医生姓名、职称、联系电话…

【Python】专栏文章索引

为了方便 快速定位 和 便于文章间的相互引用等 作为一个快速准确的导航工具 Python 目录&#xff1a; &#xff08;一&#xff09;装饰器函数 &#xff08;二&#xff09;牛客网—软件开发-Python专项练习 &#xff08;三&#xff09;time模块

数据结构与算法第三套试卷

1.删除链表节点 **分析&#xff1a;**首先用指针变量q指向结点A的后继结点B&#xff0c;然后将结点B的值复制到结点A中&#xff0c;最后删除结点B。 2.时间复杂度的计算 **分析&#xff1a;**当涉及嵌套循环的时候&#xff0c;我们可以直接分析内层循环即可&#xff0c;看内…

猫头虎分享已解决Bug || 批处理错误:BatchJobFailure, ProcessingDelay

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

太阳辐射环境模拟系统系统

太阳辐射环境模拟系统是一种高度专业化的设备&#xff0c;用于模拟太阳光的全谱段辐射&#xff0c;包括紫外线、可见光和红外线。这种系统的核心功能是在实验室条件下复制太阳的辐射条件&#xff0c;以评估材料、产品或设备在实际太阳辐射影响下的性能和耐久性。 应用领域&…

“比特币深夜冲破7万美元”!华尔街押注比特币:究竟是牛市墙头草,还是加密真信徒?

比特币ETF&#xff0c;使此次加密牛市与以往的繁荣、萧条周期截然不同。以往的周期往往由热衷风险的投机者以及最终崩盘的加密项目所驱动&#xff0c;例如无实物资产支持的加密货币借贷&#xff0c;以及一地鸡毛的ICO热潮。而现在&#xff0c;传统金融已经与加密世界联姻&#…

前端手册-实现挂坠灯笼效果

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

专题一 - 双指针 - leetcode 202. 快乐数 | 简单难度

leetcode 202. 快乐数 leetcode 202. 快乐数 | 简单难度1. 题目详情1. 原题链接2. 基础框架 2. 解题思路1. 题目分析2. 算法原理3. 时间复杂度 3. 代码实现4. 知识与收获 leetcode 202. 快乐数 | 简单难度 1. 题目详情 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」…

linux守护程序

概述 周末还要加班写代码&#xff0c;偷个懒发个刚刚写的守护进程&#xff0c;有一个小bug懒得处理&#xff0c;急着要用&#xff0c;发出来记录一下成果。 守护程序 网上很多介绍的&#xff0c;大家有兴趣自己去查查 上酸菜 #include <stdio.h> #include <stdli…

代码随想录刷题笔记-Day32

1. 最大子序和 53. 最大子数组和https://leetcode.cn/problems/maximum-subarray/ 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组&#xff1a;是数组中的一个连续…

Java学习笔记NO.18

T1.理工超市 &#xff08;1&#xff09;题目描述 编写一个程序&#xff0c;设计理工超市功能菜单并完成注册和登录功能的实现。显示完菜单后&#xff0c;提示用户输入菜单项序号。当用户输入<注册>和<登录>菜单序号时模拟完成注册和登录功能&#xff0c;最后提示…

多态的原理

通过监视可以发现&#xff0c;基类和子类的虚表指针指向的是不同的虚表&#xff08;监视窗口可以证实&#xff09;&#xff0c;而且虚表里面的函数地址也是不一样的。这就符合我们的预期了&#xff0c;因为多态的调用的时候&#xff0c;就是通过虚表指针去找到对应虚表里面的虚…

蓝桥杯练习系统(算法训练)ALGO-981 过河马

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 在那个过河卒逃过了马的控制以超级超级多的走法走到了终点之后&#xff0c;这匹马表示它不开心了……   于是&#xff0c…

[2024-03-09 19:55:01] [42000][1067] Invalid default value for ‘create_time‘【报错】

这个错误可能是因为你的 MySQL 数据库版本不支持 CURRENT_TIMESTAMP 作为默认值。在一些早期版本中&#xff0c;MySQL 对 TIMESTAMP 类型字段的默认值设置有限制&#xff0c;只允许使用特定的常量值&#xff08;如 0000-00-00 00:00:00 或 CURRENT_TIMESTAMP()&#xff09;。如…

王道机试C++第 4 章 字符串:字符串内容续写几个小程序 Day30

统计字符 习题描述 统计一个给定字符串中指定的字符出现的次数。 输入描述&#xff1a; 测试输入包含若干测试用例&#xff0c;每个测试用例包含2行&#xff0c;第1行为一个长度不超过5的字符串&#xff0c;第2行为一个长度不超过80的字符串。注意这里的字符串包含空格&…