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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全链路Python环境迁移

全链路Python环境迁移 在当前的Python环境中&#xff0c;安装一些库以后&#xff0c;如果换了一套Python环境&#xff0c;难道再来一次不停的pip install&#xff1f;当然不是。 第一步&#xff0c;使用pip freeze&#xff08;冻结&#xff09;备份当前Python库的环境 pip f…

智慧公厕系统的运作过程

智慧公厕是一种新型的未来城市公共厕所&#xff0c;通过物联网、互联网、大数据、云计算、自动化控制等技术&#xff0c;实现公共厕所使用、运营、管理、养护的全过程全方位信息化。 那么&#xff0c;智慧公厕是如何运作的&#xff1f;智慧公厕的运作过程包括什么技术&#xf…

【Pytorch、torchvision、CUDA 各个版本对应关系以及安装指令】

Pytorch、torchvision、CUDA 各个版本对应关系以及安装指令 1、名词解释 1.1 CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA开发的用于并行计算的平台和编程模型。CUDA旨在利用NVIDIA GPU&#xff08;图形处理单元&#xff09;的强大计算…

使用R语言进行聚类分析

一、样本数据描述 城镇居民人均消费支出水平包括食品、衣着、居住、生活用品及服务、通信、文教娱乐、医疗保健和其他用品及服务支出这八项指标来描述。表中列出了2016年我国分地区的城镇居民的人均消费支出的原始数据&#xff0c;数据来源于2017年的《中国统计年鉴》&#xf…

Publii和GitHub:搭建个人网站的完美组合

在数字时代&#xff0c;拥有一个个人网站已经非常普遍了&#xff0c;但是&#xff0c;很多人因为技术难题而望而却步。现在&#xff0c;有了Publii&#xff0c;这一切都将变得简单。Publii是一个静态网站生成器&#xff0c;它允许你在本地计算机上创建和管理内容&#xff0c;然…

ARM中汇编语言的学习(加法、乘法、除法、左移、右移、按位与等多种命令操作实例以及ARM的 N、Z、C、V 标志位的解释)

汇编概述 汇编需要学习的大致框架如下&#xff1a; 汇编中的符号 1.指令&#xff1b;能够北嘁肷梢惶?2bit机器码&#xff0c;并且能够被cpui识别和执行 2.伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条指令 3.伪操作&#xff1a;不会生成指令…

如何修复advapi32.dll丢失无法启动程序的问题

如果你在运行Windows程序时遇到了“advapi32.dll丢失无法启动程序”的错误消息&#xff0c;那么这意味着你的计算机缺少这个DLL文件。在本文中&#xff0c;我们将提供一些解决方案&#xff0c;帮助你解决这个问题并恢复计算机的正常运行。 一.advapi32.dll丢失电脑的提示 关于…

软件项目试运行方案

一、 试运行目的 &#xff08;一&#xff09; 系统功能、性能与稳定性考核 &#xff08;二&#xff09; 系统在各种环境和工况条件下的工作稳定性和可靠性 &#xff08;三&#xff09; 检验系统实际应用效果和应用功能的完善 &#xff08;四&#xff09; 健全系统运行管理体制&…

宏碁掠夺者:4K144Hz显示器,让你爽翻天

大家好&#xff0c;我又来了。 买了PS5后&#xff0c;我发现这样的主机放在客厅里可玩性不太高&#xff08;我没机会玩&#xff09;。 毕竟家里还有今年要上小学的孩子。 每天回家打卡交作业都让我发疯。 客厅里放一台PS5无疑是每天对孩子最大的影响&#xff08;也划破了我的心…

vue 下载的插件从哪里上传?npm发布插件详细记录

文章参考&#xff1a; 参考文章一&#xff1a; 封装vue插件并发布到npm详细步骤_vue-cli 封装插件-CSDN博客 参考文章二&#xff1a; npm发布vue插件步骤、组件、package、adduser、publish、getElementsByClassName、important、export、default、target、dest_export default…

智能驾驶规划控制理论学习08-自动驾驶控制模块(轨迹跟踪)

目录 一、基于几何的轨迹跟踪方法 1、基本思想 2、纯追踪 3、Stanly Method 二、PID控制器 三、LQR&#xff08;Linear Quadratic Regulator&#xff09; 1、基本思想 2、LQR解法 3、案例学习 基于LQR的路径跟踪 基于LQR的速度跟踪 4、MPC&#xff08;Mode…

day59 线程

创建线程的第二种方式 实现接口Runnable 重写run方法 创建线程的第三种方式 java.util.concurrent下的Callable重写call()方法 java.util.concurrent.FutureTask 创建线程类对象 获取返回值 线程的四种生命周期 线程的优先级1-10 default为5&#xff0c;优先级越高&#xff0c…

基于梯度统计学的渐变型亮缝识别算法

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、场景痛点 在图像处理相关的实际工程中&#xff0c;会出现各式各样的现实复杂问题&#xff0c;有的是因为机械设计导致&#x…

【洛谷 P8668】[蓝桥杯 2018 省 B] 螺旋折线 题解(数学+平面几何)

[蓝桥杯 2018 省 B] 螺旋折线 题目描述 如图所示的螺旋折线经过平面上所有整点恰好一次。 对于整点 ( X , Y ) (X, Y) (X,Y)&#xff0c;我们定义它到原点的距离 dis ( X , Y ) \text{dis}(X, Y) dis(X,Y) 是从原点到 ( X , Y ) (X, Y) (X,Y) 的螺旋折线段的长度。 例如 …

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

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

Java教程:RabbitMq讲解与SpringBoot项目如何对接RabbitMq实现生产者与消费者

在往期文章中&#xff0c;我们讲了如何在Windows与Linux环境下安装RabbitMq服务&#xff0c;并访问Web管理端。 有很多同学其实并不知道RabbitMq是用来干嘛的&#xff0c;它起到一个什么作用&#xff0c;并且如何在常见的SpringBoot项目中集成mq并实现消息收发&#xff0c;本章…

Nginx实现高并发

注&#xff1a;文章是4年前在自己网站上写的&#xff0c;迁移过来了。现在看我之前写的这篇文章&#xff0c;描述得不是特别详细&#xff0c;但描述了Nginx的整体架构思想。如果对Nginx玩得透得或者想了解深入的&#xff0c;可以在网上找找其他的文章。 ......................…

day17_订单(结算,提交订单,支付页,立即购买,我的订单)

文章目录 订单模块1 结算1.1 需求说明1.2 获取用户地址1.2.1 UserAddress1.2.2 UserAddressController1.2.3 UserAddressService1.2.4 UserAddressMapper1.2.5 UserAddressMapper.xml 1.3 获取购物项数据1.3.1 CartController1.3.2 CartService1.3.3 openFeign接口定义 1.4 环境…

NIFI从Oracle11G同步数据到Mysql_亲测可用_解决数据重复_数据跟源表不一致的问题---大数据之Nifi工作笔记0065

首先来看一下整体的流程: 可以看到了用到了上面的这些处理器,然后我们主要看看,这里之前 同步的时候,总是出现重复的数据,奇怪. 比如源表中只有166条数据,但是同步过去以后变成了11万条数据了. ${db.table.name:equals(table1):or(${db.table.name:equals(table2)})} 可以看…

【精选好刊】JCR2区SCI仅17天上线见刊,最后10篇版面!

录用案例 JCR2区地质环境类SCI&EI (进展顺) 【期刊简介】IF&#xff1a;3.0-4.0&#xff0c;JCR2区&#xff0c;中科院3/4区&#xff1b; 【检索情况】SCI&EI双检&#xff1b; 【征稿领域】地球观测、环境监测和管理相关或结合研究均可&#xff1b; 【案例分享】重…