Maco

闭花羞月

嗨,我是张志方 (@Maco),一名 iOS 开发者。


ARC内存管理规则

现阶段移动端iOS开发基本都是使用arc模式进行开发,但是在有的情况下也是需要使用MRC的,比如在使用系统没有加入ARC管理内存的类的时候,比如使用CoreFoundation下的文件就需要我们使用CFRelease手动释放内存,今天我们不说MRC,先了解ARC在iOS下的实现,如有不足之处,烦请评论区指出,谢谢。

思考方式

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

所有权修饰符

所谓对象类型就是指向NSObject这样的Objective-C类的指针,例如“NSObject *”,id类型用于隐藏对象类型的类名部分,相当于C语言中常用的“void *”。

__strong修饰符

是id类型和对象类型默认的所有权修饰符。

__strong修饰符表示对象的强引用,持有强引用的对象超出其作用域时被废弃,随着强引用的实效,引用的对象会随之释放。

{
	//自己生成并持有对象
	id __strong obj = [[NSObject alloc] init];
	//因为变量obj为强引用,所以自己持有对象
}

//因为变量obj超出其作用域,强引用失效,所以自动地释放自己持有的对象,对象的所有者不存在,因此废弃该对象

{
 	//取得非自己生成并持有的对象	
	id __strong obj = [NSMutableArray array];
	//因为变量obj为强引用,所以自己持有对象
}

//因为变量obj超出其作用域,强引用失效,所以自动地释放自己持有的对象

__strong 不仅在变量作用域中,在赋值上也能够正确滴管理其对象的所有者。

__weak修饰符

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

id text = [[Test alloc] init];

[test setObject:test];

该对象持有自身,也会发生循环引用

若引用不能持有对象实例

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

编译会发出警告,变量obj持有对对象的若引用,生成的对象会立即被释放。

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

Id __weak obj1 = obj;

__weak 修饰符的变量不持有对象,所以在超出其变量作用域时,对象即被释放。

id __weak obj = nil;

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

__unsafe_unretained修饰符

不安全的所有权修饰符,附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

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

Id __unsafe_unretained obj = nil;

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

//这里obj0变量超出作用域,强引用失效,自动释放自己持有的对象,对象无持有者,所以废弃该对象。

//obj变量表示的对象已经被废弃,但是指针仍然指向原来的地址(悬垂指针),错误访问

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

比如在iOS4以及OS X Snoe Leopard的应用程序中,必须使用__unsafe_unretained修饰符来替代__weak修饰符。赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用会崩溃。

__autoreleasing修饰符

在ARC中使用autoreleasepool代码块

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

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

@autoreleasepool {

	//取得非自己生成并持有的对象
    __strong id obj = [[NSObject alloc] init]

	//变量obj为强引用,所以自己持有对象,并且该对象由编译器判断其方法名后自动注册到autoreleasepool
}     //因为变量obj超出作用域,强引用失效,自动释放自己持有的对象,随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象被自动释放,对象的所有者不存在,所以废弃对象。

取得非自己生成并持有的对象时,如同上面代码,该对象已被注册到了autoreleasepool中,这同在ARC无效时取得调用了autorelease方法的对象一样,这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。

使用初始化对象作为返回值,则使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。在ARC可用情况下,init函数返回的初始化对象不会注册到autoreleasepool上。

在访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象。

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

id obj 和 id __strong obj完全一样,那么id的指针id *obj如何呢?可以由 id __strong obj的例子类推出id __strong *obj吗?其实推出来的是id __autoreleasing *obj。同样,对象指针NSObject **obj便成为了NSObject * __autoreleasing *obj。

id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符

NSError *error = nil;

BOOL result = [obj performOperationWithError:&error];

该方法声明为

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

同等于

-(BOOL) performOperationWithError:(NSError * __autoreleasing *)error;

NSError *error = nil;

NSError **pError = &error;

//会产生编译器错误,赋值给对象指针时,所有权修饰符必须一致



NSError *error = nil;

NSError * __strong *pError = &error;

//编译正常,对于其他所有权修饰符也一样



那么我们使用一下代码不会警告

NSError __strong *error = nil;

BOOL result = [obj performOperationWithError:&error];

这里参数是 __ autoreleasing修饰,但是我们传的是__strong
这里编译器自动将源代码转化了
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;

当然也可以显式地制定方法参数中对象指针类型的所有权修饰符

@autoreleasPool代码块可以嵌套使用,NSAutoreleasePool类对象也可以嵌套使用。


对象型变量不能作为C语言结构体的成员,不然会引起编译错误

```c s struct Data { NSMutableArray *array; }


C语言的规约上没有方法来管理结构体成员的生命周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理内存的生存周期。

要把对象型变量加入到结构体成员中时,可强制转换为void *或是附加__unsafe_unretained修饰符。

```c
struct Data {
	NSMutableArray __unsafe_unretained *array;
}

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

显式转换id和void

在ARC无效时,像以下代码将id变量强制转换void *变量并不会有问题

id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release]; //也不会有问题

如果在ARC有效时会引起编译错误

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

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

转换为void *的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,或者更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

__bridge转换还有另外两种转换,__bridge_retained和__bridge_transfer

void *p = (__bridge_retained void *)obj;

__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象。

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

id obj = (__bridge_transfer id)p;
该源码在ARC无效时是
id obj = (id)p;
[obj retain];
[(id)p release];

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

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

Core Foundation对象于Objective-C对象没有区别,所以在ARC无效时只用简单的C语言的转换也能实现互换。另外这种转换不需要使用额外的CPU资源,被称为免费桥。

CFMutableArrayRef cfObject = nil;

{
        id obj = [[NSMutableArray alloc] init];

        cfObject = CFBridgingRetain(obj);

        CFShow(cfObject)
}

//obj超出作用域,强引用失效,引用计数为1

CFRelease(cfObject);

//将对象CFRealease,对象引用计数为0,对象被释放

如果使用__bridge代替CFBridgingRetain转换的话,在超出作用域时,引用计数为0.对象释放,再对cfObject进行release,会出现悬垂指针崩溃(这个是将Core Foundation对象转换成Foundation对象时出现的情景)

在将Foundation对象转换成Core Foundation对象时,使用__bridge代替CFBridgingRelease转换,赋值给附有__strong修饰符的变量中,发生强引用,引用计数变成2,所以在超出对象作用域时,需要对Core Foundation对象CFRelease释放,否则会发生内存泄漏

也可以使用__bridge_retained转换

CFMutableArrayRef cfObject1 = (__bridge_retained CFMutableArrayRef)obj;

也可以使用__bridge_transfer转换替代CFBridgingRelease,这时Objective-C变量持有对象强引用的同时,对象通过CFRelease释放。

最近的文章

MRC内存管理

废话不多说,赞么直奔主题.1.0 苹果的实现NSObject内存申请过程+alloc+allocWithZoneclass_createInstancecalloc //分配内存块1.1 au...…

继续阅读

更早的文章

ARC内存管理

现阶段移动端iOS开发基本都是使用ARC模式进行开发,但是在有的情况下也是需要使用MRC的,比如在使用系统没有加入ARC管理内存的类的时候,比如使用CoreFoundation下的文件就需要我们...…

继续阅读