赞
踩
在学习ARC之前,先来复习一下内存管理以及autorelease的实现
先来看一下GNUstep源代码:
autorelease
其本质就是调用NSAutoreleasePool
对象的addObject
类方法,就是将对象加到自动释放池中
接下来再看一下废弃自动释放池的一些功能函数
可使用showPools
输出现在的NSAutoreleasePool的状况输出到控制台
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
id obj3 = [[NSObject alloc] init];
[obj autorelease];
[obj2 autorelease];
[obj3 autorelease];
_objc_autoreleasePoolPrint();
[pool drain];
引用计数式内存管理的思考方式就是思考ARC所引起的变化
ARC有效时,id 类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。 所有权修饰符一共有4 种
(ARC环境下特有)
_ strong修饰符是id类型和对象类型默认的所有权修饰符
也就是说id obj = [[NSObject alloc] init]; = id __strong obj = [[NSObject alloc] init];
再来看下面这段代码
//ARC有效
{
id __strong obj = [[NSObject alloc] init]
}
此源代码指定了变量的作用域,当obj超出其变量作用域时, obj会被废弃,同时自动释放其被赋予的对象([[NSObject alloc] init])
而在MRC中,等效的代码为
//ARC无效
{
id obj = [[NSObject alloc] init]
[obj release];
}
因为ARC无效的时候,obj超出变量作用域时,变量并不会被自动废弃,对象也会仍然存在,需要我们手动减少对象的引用计数[obj release]
去销毁对象
具体如下:
这里需要注意的一点是此处obj确实持有了对象,并且对象的引用计数为1,但是在目前版本的Xcode的MRC
环境中,
{
id obj = [NSMutableArray array];
NSLog(@"%lu", [obj retainCount]);
}
在MRC环境下输出的值应该为0,因为array方法表明取得非自己生成并持有的对象,也就是说obj并不持有对象,但是输出如下:
我们来解释一下输出为1的原因:
当我们调用 retainCount 方法时,对于从自动释放池获取的对象,它会临时retained一次,以防止对象被过早释放而导致访问过期数据。
1.array
方法创建了一个对象,并将其加到自动释放池中,此时retain count
为0
2.obj指向自动释放池中的那个对象,并没有对对象进行retain操作,只是持有了一个指向他的指针
3.调用 [obj retainCount]
时 ,a.编译器会临时保留(retain)对象 b.获得并输出其retain count值 c.释放对象
所以尽管对象最初的 retain count 为 0,但由于 retainCount 方法的实现机制,它会临时保留对象来避免崩溃,这导致我们看到的输出 retain count 为 1。
id __strong obj0 = [[NSObject alloc] init];//对象A
id __strong obj1 = [[NSObject alloc] init];//对象B
id __strong obj2 = nil;
obj0 = obj1;//obj0持有有obj1赋值的对象B的强引用,obj0被赋值。所以原先持有的a的强引用失效,此时b的强引用变量为obj1,obj0
由此__strong 修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。
重点是当Test对象释放时,Test对象的obj_成员变量也会随之被释放
使用weak可以使我们取得对象但是并不持有对象
下面有一个代码例子进行解释
id a = [[NSObject alloc] init];
id __weak b = a;
id c = a;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)a));
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
a = nil;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
可以看到在ARC环境下a,b,c都指向了对象,但是引用计数只有2,这是因为weak是指向对象的指针但并不持有对象,并不会使引用计数加1
当我们将weak修饰符改为strong时,就会出现如下结果
id a = [[NSObject alloc] init];
id __strong b = a;
id c = a;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)a));
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
a = nil;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
内存管理中会发生循环引用的问题,此时就需要用到__weak修饰符
正确的内存释放过程:
首先B对象是A对象的一个属性,也就是A持有B,现在要释放掉A,需要给A发送一个release消息,这时A的引用计数变为0,就要走delloc方法,delloc方法会对A所持有的全部对象发送release消息,当然也包括B,也就是对B进行release,此时B的引用计数也变为0,然后执行delloc,最后A与B都被释放掉了
- (void)dealloc {
[_b release]; // 释放持有的 B 实例
_b = nil;
[super dealloc];
}
循环引用的产生:
解释:对象之间互相持有,形成闭环,导致谁也无法被正确释放
循环引用容易发生内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。
过程:
如此循环往复,对象之间都在等对方给自己发送release消息,导致谁也无法执行,如此往复便造成了循环引用
当然循环引用并不只出现在变量中,还出现在协议与block中,后面在学习的过程中会专门写博客记录
接下来我们谈论一下如何解决这类问题:
因为我们知道weak可以使变量取得但并不持有对象,也就是说不会增加对象的引用计数,我们将对象中的属性用weak修饰符修饰就可以解决这个问题
使用weak时因为变量不持有对象,因此不会造成相互引用,当对象释放后weak变量会自动置为nil,也避免了野指针的情况
#pragma mark 持有对象的弱引用 MRC 在MRC下,没有__weak这样的自动nil置化特性 使用weak持有某对象弱引用时,对象被废弃,弱引用变量自动只为nil
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
__unsafe_unretained
是一个不安全的所有权修饰符,在MRC下使用来避免循环引用
但是与weak相比,其会产生悬垂指针
因此我们在使用**__unsafe_unretained必须保证对象存在**
__unsafe_unretained
来代替__weak
.ARC中不能使用autorelease方法以及NSAutoreleasePool类,但是实际上ARC有效时autorelease功能还是有作用的
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
autorelease
,即将对象注册到autoreleasepool@autoreleasepool f
id __strong obj = [NSMutableArray array];
}
因为weak修饰符只持有对象的弱引用,因此访问引用对象时对象可能被遗弃。所以我们将对象注册到autoreleasepool可以确保对象存在
当将对象注册在autoreleasepool中,autoreleasepool会临时保留这个对象,直到作用域结束
另外在书上讲id *obj = id __autoreleasing *obj
,以此类推NSObject **obj
便成为了NSObject * _autoreleasing *obj
,这里我们需要知道NSObject *__autoreleasing t1与NSObject __autoreleasing *t1有本质的不同:
在自动引用计数(ARC)管理的 Objective-C 环境中,当你使用双重指针(比如 NSError **error)作为方法参数时,ARC 会假定这个指针指向的对象是 __autoreleasing
总结来说,NSObject *__autoreleasing t2 是一个自动释放的对象指针,而 NSObject
__autoreleasing *t1 是指向一个自动释放对象指针的指针。
我们以一个代码例子来实验一下
@interface ViewController : UIViewController @property (nonatomic, weak)NSObject *Obj1; @property (nonatomic, weak)NSObject *Obj2; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self test1]; } - (void)test1 { NSLog(@"%@ %@", self.Obj1, self.Obj2); [self test2]; NSLog(@"%@ %@", self.Obj1, self.Obj2); } - (void)test2 { NSObject *t1 = [[NSObject alloc] init]; NSObject *__autoreleasing t2 = [[NSObject alloc] init]; self.Obj1 = t1; self.Obj2 = t2; NSLog(@"%@ %@", self.Obj1, self.Obj2); }
这段代码中因为test2中,t1超出变量作用域,同时self.obj1
是被weak修饰的,并不持有对象,对象超出作用域自动销毁,因此第三行输出null。
而t2由于使用了 __autoreleasing,它的生命周期被延长到当前的自动释放池结束
它的生命周期被延长到当前的自动释放池结束"这句话的含义是:
@interface ViewController : UIViewController @property (nonatomic, strong)NSObject *Obj1; @property (nonatomic, strong)NSObject *Obj2; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self test1]; } - (void)test1 { NSLog(@"%@ %@", self.Obj1, self.Obj2); [self test2]; NSLog(@"%@ %@", self.Obj1, self.Obj2); } - (void)test2 { NSObject *t1 = [[NSObject alloc] init]; NSObject *__autoreleasing t2 = [[NSObject alloc] init]; self.Obj1 = t1; self.Obj2 = t2; NSLog(@"%@ %@", self.Obj1, self.Obj2); }
而这段代码中obj用了strong修饰,即使t1,t2作为局部变量超出了作用域,但是self.Obj1仍然持有这个对象,因此这个对象并不会被销毁,因此其仍然存在
dealloc 方法在大多数情况下还适用于删除已注册的代理或观察者对象。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。