MRC
对象持有讨论
先看一个例子:
// 这个方法生成对象并返回
- (id)object
{
// 通过 alloc/new/copy/mutableCopy 方法生成对象并自己持有
id obj = [[NSObject alloc] init];
/**
autorelease使得自己不去持有这个对象
自己持有对象的话,可以通过[obj release] 去释放对象,但当对象自身autorelease时,释放是由自身自动释放的,就不能通过release了(个人理解),这个时候去调用release会造成崩溃.
*/
[obj autorelease];
return obj;
}
// 方法调用:
/**
这里通过方法 object获取到对象,这个对象是存在的,但是自己并不持有这个对象,对自己不持有的对象是不能去释放他的.
这个对象为什么存在,上面不是自己加了autorelease吗?
其实这就是autorelease和release的区别:调用release会立即释放,但autorelease不会,调用autorelease会将对象注册到autoreleasepool中,当pool结束时会自动调用release.这里其实存在着一个哨兵指针. autoreleasepool 的释放是 NSRunloop 决定的.具体可以参考杨潇玉的博客.
*/
id obj1 = [obj0 object];
// 如果让obj1区持有这个对象呢? 可以调用retain,他可以将调用autorelease方法取得的对象变为自己持有
[obj1 retain];
总结:释放非自己持有的对象会造成程序崩溃,因此绝不能释放非自己持有的对象
关于内存释放的研究
NSAutoreleasePool
什么时候释放?NSAutoreleasePool
的生命周期是怎样的?是在方法体调用结束就释放?其实不是.
在大量产生autorelease
对象时,只要不废弃NSAutoreleasePool
对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象.典型例子是读入大量图像时,图像文件读入到NSData
对象,从中生成UIImage
对象.这种清空下会产生大量autorelease
对象.这种情况就需要再适当的地方生成,持有,废弃NSAutoreleasePool
对象.
比如在MRC
时可以这样写:
for(int i = 0; i<100; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 读入图像,大量产生autorelease对象
// ........
[pool drain]; // 这句代码可以让autorelease对象被一起release
}
Zone
我们经常看到不少方法里有这个单词,比如NSZoneMalloc
,NSDefaultMallocZone
等等.这个单词是干什么的呢?
NSZone
其实是为了防止内存碎片化而引入的结构.它ui内存分配的区域本身进行多重化管理,根据对象目的,大小分配内存,提高内存管理效率.
但是根据苹果官方所说,现在的运行时系统,内存管理的效率本身已经很高,使用区域来管理内存反而会引起内存使用效率低下及源代码复杂化.
ARC
首先看看ARC中的4个修饰符:
- __strong (默认修饰符)
- __weak
- __unsafe_unretained
- __autoreleasing
在MRC中有个问题,通过alloc的方式得到的对象会被自己所持有,而通过比如NSArray
的array
方法得到的对象不会被持有:
** MRC **
// obj持有对象
id obj = [[NSObject alloc] init];
// array不持有对象
NSArray *arr = [NSMuableArray array];
那在ARC中是怎么样的呢?
** ARC **
// obj持有对象
id obj = [[NSObject alloc] init];
// 相当于:
id __strong obj = [[NSObject alloc] init]; // 由于obj为强引用,所以自己持有对象
/**
这里的array也是持有对象的.
array类方法让arr取得 非自己生成并持有 的对象
但因arr为强引用,所以持有对象
在超出作用域(方法体)后,强引用失效,会自动释放自己所持有的对象
*/
NSArray *arr = [NSMuableArray array];
// 相当于
NSArray *__strong arr = [NSarray array];
赋值方法对对象的引用
__strong
id __strong objA = [[NSObject alloc] init]; // objA持有对象A的强引用
id __strong objB = [[NSObject alloc] init]; // objB持有对象B的强引用
id __strong objC = nil; // objC不持有任何对象
/**
objA持有由objB赋值对象B的强引用
因为objA被赋值,所以原先持有的对象A的强引用失效
对象A的所有者不存在,因此废弃对象A
此时,持有对象B的强引用的变量为
objA 和 objB
*/
objC = objA;
/**
objC 持有由objA赋值的对象B的强引用
此时,持有对象B的强引用的变量为
objA 和 objB 和 objC
*/
objB = objC;
/**
nil被赋予了objB,所以objB对对象B的强引用失效
此时,持有对象B的强引用的变量为
objA 和 objC
这里做一个理解,为什么objB = nil, objA还是和objC还是指向对象B呢? objA和objC 会变成nil吗?
不会.objB = nil;这句话的意思是objB抛弃了原先的对象,重新指向了一个对象(nil),原先的对象B会通过retainCount - 1 的方式回应这次'抛弃'. 但如果B不是通过从新指向另一块内存的方式,而是改变自身,比如 [objB addObject],那么他是对自身对象B进行的操作,这个时候objA和objC也会相应被改变.
*/
objB = nil;
总结:
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也可以持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
__weak
__strong
修饰符基本可以完美的进行内存管理了,但是遇到循环引用就需要__weak
了.
比如两个Person
实例 personA
和personB
,都有一个id
类型的obj
属性, obj
属性被分别赋值 personB
和personA
,即:
Person personA = [[Person alloc] init]; // A 对象
Person personB = [[Person alloc] init]; // B 对象
personA.obj = personB;
personB.obj = personA;
这个时候A
对象的持有者是两个 personA
和 personB
的obj
属性;
B
对象的持有者也是两个 personB
和 personA
的obj
属性;
当personA
变量超出其作用域,强引用失效,自动释放对象A
, 当personB
变量超出其作用域,强引用失效,自动释放对象B
. 此时,持有 A
对象的强引用变量为对象B
的obj
,持有B对象的强引用为对象A的obj
. 发生内存泄漏.
所谓内存泄漏,就是应当废弃的对象在超出其生命周期后继续存在.
上面的例子,如果A 对象持有自身,即personA.obj = personA
,这样也会造成循环引用.
还有一个例子,这里做一下分析:
id __weak objA = nil;
{
id __strong objB = [[NSObject alloc] init];
objA = objB;
NSLog(@"A = %@",objA);
}
NSLog(@"A = %@",objA);
输出结果为:
A = <NSObject: 0x45f140e>
A = (null)
对于id __weak objA = nil;
这句代码,我之前也一直不理解,先不论objA是否=nil, __weak修饰的对象如果没有强持有者不会立即释放掉吗,为什么objA 还存在?
其实,objA
是指针,是在栈里的,objA
指向的对象(比如对象A
)是在堆内存里的,确实,对象A释放后objA
也会随之销毁,但是由于objA
是在栈里的,并且objA
其实是autorelease
的,该指针只会被标记为要释放,等待autorelease
要释放(drain
)时候,objA
才会通过哨兵对象确定要被释放,从而发送release
消息将它释放掉,所以在这个方法体里,objA
还是存在的.所以,在使用附有__weak
修饰符的变量时就必定要使用注册到autorelepool
中的对象.其实,__weak申明的变量会自动将对象注册到autoreleasepool中.
所以,第一次打印,是打印的objA
通过弱引用持有的对象,这个对象在方法体结束后被释放了,所以第二次打印为null
.
<font size='3' color='0000ff'>weak自动置为nil的实现</font>
id __weak obj1 = obj;
这句话做了什么?
- 首先,obj1通过obj进行初始化,调用函数
objc_initWeak(&obj1, obj)
当释放的时候,第二个参数传递0,即
objc_destroyWeak(&obj1, 0)
-
objc_storeWeak
函数把obj1
的地址和被赋值对象通过键值对的方式注册到weak表里.当对象释放的时候,通过废弃对象的地址作为键值进行检索,就能获取到对应__weak
变量的地址,然后将所有附有__weak
修饰的变量地址全部赋值nil
,之后再把她从weak
表中移除掉就好了. - 大量使用
__weak
会消耗相应的CPU资源(要注册weak
表,并且一个键值,可以注册多个变量的地址),所以一般只在需要避免循环引用的时候使用__weak
.
__unsafe_unretained
这个修饰符是不安全的.ARC的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器内存管理的对象.
同样用上面的例子做说明,第一行换成id __unsafe_unretained objA = nil;
打印结果为:
A = <NSObject: 0x45f140e>
A = <NSObject: 0x45f140e>
奇怪,第二次打印结果正常,这是怎么回事?
当方法体出来后,objB强引用失效,自动释放自己持有的对象,这个时候没有强引用去持有这个对象,对象释放.所以objA变量表示的对象已经被废弃,变成悬垂指针,这是一个错误访问.所以这一次的打印只是碰巧正常运行而已.虽然访问了已经被废弃的对象,但是应用程序在个别运行状况下才会崩溃.
__autoreleasing
ARC下,autorelease是不能用的(这个修饰符是可以使用的),包括NSAutoreleasePool类也不能用,MRC下是这样使用的:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
drain方法废弃正在使用的NSautoreleasePool对象的过程为:
- (void)drain
{
[self dealloc];
}
- (void)dealloc
{
[self emptyPool];
[array release];
}
- (void)emptyPool
{
for (id obj in array){
[obj release];
}
}
ARC下,该源代码写成如下这样:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init]; // ARC无效时会调用autorelease方法.另外,__autoreleasing修饰符一般是可以非显示地使用的
}
在使用alloc/new/copy/mutableCopy
以外的方法来取得对象的时候,该对象会被注册到autorelpool.编译器会检查方法名是否以这几个单词开始,如果不是的则自动将返回的对象注册到autoreleasepoll
.
另外,init
方法返回值的对象不会注册到autoreleasepool
.
不管是否使用了ARC,调试用的非公开函数_objc_autoreleasePoolPrint()
都是可以使用的.不过有可能报错Implicit declaration of function - C99
,这种情况可以通过Build Setting -> C Language Dialect
修改为GNU99
或GNU89
解决.
使用ARC的时候,对象型变量补鞥呢作为C语言结构体(struct/union)的成员.因为ARC把内存管理的工作交给编译器,那么编译器必须知道并管理对象的生存周期.要把对象型变量加入到结构体成员中时,可以强制转换为void *
或者附加__unsafe_unretained
修饰符(因为__unsafe_unretained
修饰符修饰的变量不属于编译器的内存对象).
** MRC **
id obj = [[NSObject alloc] init];
void *p = obj;
// 也可以反过来赋值
id obj2 = p;
[obj2 release];
** ARC **
// 要使用__bridge桥接转换
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id obj2 = (__bridge id)p;