内存管理之MRCARC

内存管理

主要有以下四点:

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

对象操作与其Objective-C方法的对应:

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

分别介绍这四点:(代码均为MRC环境)

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

使用alloc/new/copy/mutableCopy方法

id obj = [[NSObject alloc] init];

以alloc/new/copy/mutableCopy为前缀的方法也可以

id obj = [NSObject newObject];

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

即使用alloc/new/copy/mutableCopy 以外的方法

id obj = [NSMutableArray array];

NSMutableArray的类对象赋值给obj,但NSMutableArray非obj自己生成且不持有,需要调用retain方法,即:

//取得的对象存在但不持有
id obj = [NSMutableArray array];
//持有该对象
[obj retain];

不再需要自己持有的对象就将其释放

自己持有的对象如果不需要了,有义务将其使用release方法释放

自己生成并持有的对象

//自己持有对象
id obj = [[NSObject alloc] init];
//释放对象
//指向对象的指针仍然被保留在变量obj中,貌似可以访问,但对象一经释放绝对不可访问
[obj release];

非自己生成并持有的对象

//取得的对象存在但不持有
id obj = [NSMutableArray array];
//持有该对象
[obj retain];
//释放对象
//对象不可再被访问
[obj release];

注意

在这里插入图片描述
注意 有可能会出现以上那种释放后还是可以访问的情况,那是因为obj指针依然存在并且指向那段内存,而内存还没有被占用,所以可以访问到,有时又会报错,相当于碰运气…

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

如果不是自己持有的对象一定不能进行释放,倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。

释放两次

例如自己生成并持有对象后,在释放完不再需要的对象之后再次释放(释放两次)

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

在这里插入图片描述
对象已经被释放了,即自己不持有了, 再次释放就会导致崩溃

本就不持有

取得的对象存在,但自己不持有对象时进行释放

id obj = [NSMutableArray array];
//释放非自己持有的对象,程序崩溃
[obj release];

在这里插入图片描述

retainCount

对象的引用计数可以用retainCount来取得
例如下面这段代码

//obj自己生成且持有该对象 retainCount+1 即为1
id obj = [[NSObject alloc] init];
NSLog(@"%lu", [obj retainCount]);
//obj1不持有该对象
id obj1 = obj;
//obj1持有该对象 retainCount+1 即为2
[obj1 retain];
NSLog(@"%lu", [obj retainCount]);
//obj1释放该对象 retainCount-1 即为1
[obj1 release];
NSLog(@"%lu", [obj retainCount]);

在这里插入图片描述
但是,我们在开发中不应使用此方法。该方法所返回的保留计数只是这一时间点的值,并没有考虑到系统会稍后把自动释放池清空,因而不会将后续的释放操作从返回值里减去,此值就未必是实际的数值了。例如这段代码:

while([obj retainCount]) {
	[obj release];
}

错误一:它没有考虑到后续的自动释放操作,只是不停地释放来降低保留计数。假如此对象也在自动释放池里,那么稍后系统清空自动释放池时就会崩溃
错误二:retainCount可能永远不返回0,有时系统会优化对象的释放行为,在保留计数还是1的时候就将对象回收,那么while循环永远不会停止。所以,就算有时可能正常运行也是靠运气。

autorelease

autorelease即“自动释放”,类似于C语言中的局部变量。C语言的局部变量:程序执行时,若某局部变量超出其作用域,该局部变量自动废弃。

{
	int a;
}
//此处超出变量a的作用域,局部变量a被废弃,不可再访问

autorelease像C语言的局部变量那样对待对象实例。当超出其作用域时,对象实例的release实例方法被调用,释放该变量。

使用方法

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

NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于调用过autorelease方法的对象,在自动释放池废弃时,所有对象都将调用release方法

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
//执行该步时自动释放池中的对象释放
[pool drain];

适用场景

尽管NSAutoreleasePool会自动释放对象,但在大量产生autorelease对象并且自动释放池还没释放时,还是会生成大量对象,造成内存不足。举个例子,我们需要在读入大量图像的同时改变其尺寸。图像文件读入到NSData对象,生成UIImage对象,改变尺寸后生成新的UIImage对象。就会产生大量的autorelease对象。

for(int i = 0; i < 100000; i++) {
   //读入图像
   //产生autorelease对象
}

如果我们在循环体中加入自动释放池,即一个循环内会将生成的autorelease对象释放,内存就会节省很多

for(int i = 0; i < 100000; i++) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   //读入图像
   //产生autorelease对象
   //autorelease对象被释放
   [pool drain];
}

其他方法

Cocoa框架中也有很多类方法用于返回autorelease对象,比如:

id array = [NSMutableArray arrayWithCapacity:1];
//等效于
id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

我当时看到这里的时候有一个疑问:
第一个方法是arrayWithCapacity:很明显非array自己生成并且不持有,为什么说等同于第二句的alloc方法呢
因为第一个类方法返回的是autorelease对象,而第二个方法也调用了autorelease方法, 生成了autorelease对象,即返回值相同

showPools

可以通过自动释放池类中的调试用的showPools 方法来确认已经被autorelease的对象的状况。但是showPools方法只能在iOS中使用,现在我们使用非公开函数_objc_autoreleasePoolPrint()

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
extern void _objc_autoreleasePoolPrint(void);
_objc_autoreleasePoolPrint();
[pool drain];

在这里插入图片描述

使用showPools时使用showPools时

ARC自动引用计数

所有权修饰符

ARC有效时,id类型和对象类型必须附加所有权修饰符(默认为__strong),修饰符有四种:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

__strong修饰符

__strong修饰符是默认的所有权修饰符,即:

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

__strong修饰符见名知意,表示对对象的“强引用”, 持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放,即:

{
//因为变量obj为强引用,所以持有对象
	id __strong obj = [[NSObject alloc] init];
}
//超出作用域后强引用失效,对象所有者不存在,被自动释放

对于非自己生成的对象:

{
//因为变量obj为强引用,所以持有对象
	id __strong obj = [NSMutableArray array];
}
//超出作用域后强引用失效,对象所有者不存在,被自动释放

附有__strong修饰符的变量也可以互相赋值

id obj = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
id obj3 = nil;
obj3 = obj2;
obj2 = obj;

附有__strong修饰符同__weak修饰符,__autorelease修饰符一起,可以保证将附有这些修饰符的自动变量初始化为nil,即:

id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;
//等同于
id __strong obj1 = nil;
id __weak obj2 = nil;
id __autoreleasing obj3 = nil;

__weak修饰符

__weak修饰符一般用于解决“循环引用”的问题,例如这段代码就会形成循环引用:

@interface MyObject : NSObject

@property (nonatomic) id object;

- (void)setObject:(id __strong) obj;

@end

@implementation MyObject 

- (instancetype)init
{
    self = [super init];
    return self;
}

- (void)setObject:(id __strong) obj {
    _object = obj;
}

@end

循环引用部分:

{
    id test1 = [[MyObject alloc] init];//对象A
    id test2 = [[MyObject alloc] init];//对象B
    [test1 setObject:test2];
    [test2 setObject:test1];
}

我们可以看出,持有对象A的变量为test1和test2的obj,同理,持有对象B的变量为test2和test1的obj
test1和test2超出作用域后,强引用失效,自动释放对象,但test1的obj和test2的obj都没有释放,发生内存泄漏!
这时候就需要用到__weak修饰符了,即弱引用。

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

如果直接这么写,编译器会报警告
在这里插入图片描述
因为__weak修饰符为弱引用,如果这么写就没有变量来持有新生成的对象,该对象一经生成就会自动释放,所以应该这么写:

id __strong sObj = [[NSObject alloc] init];
id __weak wObj = sObj;

让sObj持有它
上面的内存泄漏代码就可以改成:

@interface MyObject : NSObject
//将属性改为弱引用
@property (nonatomic) id __weak object;

- (void)setObject:(id __strong) obj;

@end

@implementation MyObject 

- (instancetype)init
{
    self = [super init];
    return self;
}

- (void)setObject:(id __strong) obj {
    _object = obj;
}

@end

__weak修饰符还有一好处就是,持有某对象的弱引用,如果对象被废弃,弱引用将自动失效且为nil

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

在这里插入图片描述
附有__weak修饰符的变量,即是使用注册到自动释放池中的对象,证明:

    id obj = [[NSObject alloc] init];
    id __weak wObj = obj;
    NSLog(@"1 %@", wObj);
    NSLog(@"2 %@", wObj);
    NSLog(@"3 %@", wObj);
    NSLog(@"4 %@", wObj);
    NSLog(@"5 %@", wObj);
    _objc_autoreleasePoolPrint();

在这里插入图片描述
每访问一次会注册一次,当数量很多时,这样也会占据大量内存,就可以将附有__weak修饰符的变量赋值给附有__strong修饰符的变量后再使用可以避免此类问题

    id obj = [[NSObject alloc] init];
    id __weak wObj = obj;
    id tmp = wObj;
    NSLog(@"1 %@", tmp);
    NSLog(@"2 %@", tmp);
    NSLog(@"3 %@", tmp);
    NSLog(@"4 %@", tmp);
    NSLog(@"5 %@", tmp);
    _objc_autoreleasePoolPrint();

在这里插入图片描述
id tmp = wObj时对象仅注册到自动释放池中一次

__unsafe_unretained修饰符

正如其名,__unsafe_unretained修饰符是不安全的,且附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象
同样,附有__unsafe_unretained修饰符不能直接使用

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


与__weak修饰符不同的是:

id __unsafe_unretained obj = nil;
{
	id __strong obj0 = [[NSObject alloc] init];
	obj = obj0;
	NSLog(@"%@", obj);
}
NSLog(@"%@", obj);

在这里插入图片描述
原因是:obj变量既不持有对象的强引用也不持有对象的弱引用,当obj0失效时,对象无持有者,被释放,obj成为悬垂指针。故在使用__unsafe_unretained修饰符时一定要确保赋值的对象存在!

__autoreleasing修饰符

上面已经讲过ARC无效时autorelease的使用,在ARC有效时,可以写成这样:

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

@autoreleasepool块即相当于上文的NSAutoreleasePool类生成、持有及废弃
附有__autoreleasing修饰符相当于变量调用了autorelease方法
以下为使用__weak修饰符的例子,虽然__weak修饰符是为了避免循环引用而使用的,但在访问附有__weak修饰符的变量时,实际上必定要访问注册到AutoreleasePool的对象

id __weak wObj = sObj;
NSLog(@"%@", [wObj class]);
//等同于
id __weak wObj = sObj;
id __autoreleasing tmp = wObj;
NSLog(@"%@", [tmp class]);

因为弱引用不持有,在访问对象的过程中对象有可能被废弃,要保证正常访问就需要将其注册到自动释放池中

一般情况下,id的指针或对象的指针会默认附加上__autoreleasing修饰符,即:

(NSError **)error
//等同于
(NSError * __autoreleasing *)error

然而,下面的源代码会有编译错误:

NSError *error = nil;
NSError **pError = &error;

在这里插入图片描述
赋值给对象指针时,所有权必须一致:

NSError *error = nil;
NSError * __strong *pError = &error;

在这里插入图片描述

一些规则

ARC有效时,不能使用retain或者release

只能在ARC无效时使用retain/release/retainCount/autorelease等方法
ARC有效时也不能使用NSAllocateObject/NSDeallocateObject函数

遵守命名规则

Objective-C对方法命名一直比较严格,但是对于ARC部分,必须遵守其命名规则,即:

  • 以alloc/new/copy/mutableCopy开始命名的方法在返回对象时,必须返回给调用方所应当持有的对象
  • 以init开始命名的方法在返回对象时,返回的对象应为id类型或该方法声明类的对象类型,抑或是该类的超类或子类

不要显示调用dealloc

使用@autoreleasepool块代替NSAutoreleasePool

不能使用区域

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

要把对象型变量加入到结构体成员中时,可强制转换为void*或是附加__unsafe_unretained修饰符
但是附有__unsafe_unretained修饰符的变量不属于内存管理对象,使用时需要多注意

显式转换id和void*

如果只想单纯的赋值,则可以使用“__bridge转换”

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

__bridge转换中还有另外两种转换,“__bridge_retained转换”和“__bridge_transfer转换”
__bridge_retained转换可使要转换赋值的变量也持有所赋值的对象

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

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

参考文献

《Objective-C高级编程 iOS与OS X多线程和内存管理》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值