引用计数式的内存管理 在arc中是没有改变的。
-----2014/3/18 Beijing
在arc中,有有效和无效两种方式,我们可以在一个app中混合使用。
使用clang(LLVM编译器)或者以上版本,指定编译器属性为:"-fobjc-arc"就可以使用arc;
在oc中,引用计数式的内存管理的思考方式就是思考arc所引起的变化:
1、自己生成的对象,自己持有。
2、非自己生成的对象,自己也能持有。
3、自己持有的对象不再需要时释放。
4、非自己持有的对象无法释放。
所有权修饰符:
oc中,为了处理对象,可以将变量类型定义为id类型或各种对象类型。
对象类型就是指向NSObject这样的oc类的指针。 id类型用于隐藏对象类型的类名部分,相当于c中的void *;
arc中,需要加上修饰符:
__strong
__weak
__unsafe_unretained
__autoreleasing
1、__strong
他是id类型和对象类型默认的所有权修饰符。 也就是说 代码中的id变量实际上被附加了所有权修饰符。
id和对象类型在没有明确指定所有权修饰符的时候,默认是strong类型的:
id obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];
上面两行代码时等效的。
非arc时的比较:
{
id __strong obj = [[NSObject alloc] init];
}
{
id obj = [[NSObejct alloc] init];
[obj release];
}
为了释放生成并持有的对象,增加了调用release方法的代码。
__strong 修饰符表示对对象的“强引用”, 持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
来分析一下此段代码:
取得自己持有的对象。
{
//自己生成并持有对象
id __strong obj = [[NSObject alloc] init];
//因为变量obj为强引用
//所以自己持有对象
}
//因为变量obj超出其作用域,强引用失效
//所以自动释放自己持有的对象, 对象的多有者不存在,因此废弃该对象。
取得非自己持有的对象:
{
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用, 所以自己持有对象
}
//因为变量obj超出其作用域,强引用失效。
//所以自动地释放自己持有的对象。
在这,对象的所有者 和 对象的生存周期也是明确的。
__strong修饰符的变量之间可以相互赋值。
__strong修饰符的变量不仅在变量作用域中,在赋值上也能够正确地管理其对象的所有者。
、在oc中,我们可以使用赋有__strong修饰符的变量。
@interface Test: NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
通过__strong修饰符,无需再键入retain或者release,他们完美地满足了“引用计数式内存管理的思考方式”:
1、自己生成的对象,自己持有。
2、非自己生成的对象,自己也能持有。
3、不再需要自己持有的对象时释放。
4、非自己持有的对象无法释放。
其中1,2 通过带有__strong修饰符的变量赋值就可以完成。 废弃带__strong修饰符的变量或者对变量赋值,都可以做到:”不再需要自己持有的对象释放“。
第四项的 不是自己持有的对象释放, 由于我们不用再次键入 release 所以原本就不会执行。 他们都满足于引用计数式的内存管理的思考方式。
__strong属于默认的修饰符,所以我们不需要键入。 arc有效并简单的编程 遵循了oc的内存管理的思考方式。
2、__weak:
在引用计数的时候会产生 循环引用 的问题。
如图:
来看循环引用代码:
@interface Text : NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong) obj;
@end
@implementation Text
- (id) init {
self = [super init];
return self;
}
- (void) setObject:(id __strong)obj {
obj_ = obj;
}
@end
{
id text0 = [[Text alloc] init]; //对象a
id text1 = [[Text alloc] init]; //对象b
[text0 setObject:text1]; //对象a的obj_持有对象b的强引用, 此时持有对象b的强引用变量为 a的obj_和text1.
[text1 setObject:text0]; //对象b的obj_持有对象a的强引用, 此时持有对象b的强引用变量为b的obj_和text0
}
//因为text0 变量超出作用域,强引用失效。自动释放对象a
//因为text1 变量超出作用域,强引用失效,自动释放对象b
//此时持有对象a的强引用变量为对象b的obj_ 持有对象b的强引用的变量为 对象a的obj_ 发生内存泄露。
还有就是当你只有一个对象,该对象持有其自身的时候也会发生泄露。
__weak与strong相反,提供弱引用,他不能持有对象实例。
我们为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。
{
id __strong obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;
}
__weak修饰符的变量不持有对象,所以在超出其变量作用域时,对象即被释放。 如果像下面这样将先前可能发生循环引用的类成员变量改成附有__weak修饰的成员变量的话,就会避免循环引用。
@interface Text : NSObject {
id __weak obj_;
}
- (void)setObject:(id __strong) obj;
@end
__weak还有一个优点,在持有某对象的弱引用时,若该对象被抛弃,则此弱引用将自动失效,并处于nil被赋值的状态(空弱应用)。
在ios5 以上版本使用的是__weak, 而在ios4中使用的是__unsafe_unretained修饰符。
3、__unsafe_unretained
他是不安全的所有权修饰符。附有__unsafe_unretained修饰符的变量不属于编译器内存管理对象。
他跟__weak一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。
代码:
id __unsafe_unretianed obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
//obj0为强引用。 自己持有对象
obj1 = obj0;
//obj1变量即不持有对象的强引用也不持有弱引用。
}
//obj0 超出作用域,强引用失效。 自己释放自己持有的对象, 因为对象没有持有者,所以废弃对象。
//obj1 变量表示的对象,已经被废弃了, 悬垂指针。
4、__autoreleasing修饰符
arc有效的时候,autorelease 和 NSAutoreleasePool 都是不能直接使用的。
我们应该写成:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
}
@autoreleasepool来替代NSAutoreleasePool类对象的生成,持有以及废弃。
在arc有效的时候,要通过对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。
@autoreleasepool{
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象, 并且该对象 由编译器判断其方法名后,自动注册到autoreleasepool
}
//因为变量obj超出作用域,强引用失效,自动释放持有的对象, 同事随着@autoreleasepool的结束,注册到其中的所有对象被释放。因为对象的所有者不存在,所以废弃。
还有就是__weak修饰的变量,他在被访问的时候,必定会访问注册到autoreleasepool的对象, 因为__weak修饰符只持有对象的弱引用, 而在访问引用对象的过程中,该对象有可能被废弃,如果把要反问的对象注册到autoreleasepool中,那么在autoreleasepool块结束之前都能确保该对象存在;
来说一下__autoreleasing 如下:
NSError **error 是等同于: NSError *__autoreleasing*error的。
如下会发生错误:
NSError *error = nil;
NSError **pError = &error;
要修改加上__strong修饰符。
NSError *error = nil;
NSError *__strong*pError = &error;
NSError __weak*error = nil;
NSError *__weak*pError = &error;
下面的例子也是正确的:
NSError __strong *error = nil;
NSError **pError = &error;
其实他是被改写了:
NSError __strong*error = nil;
NSError _autoreleasing *tem = error;
NSError **pError = &tem;
在NSAutoreleasePool中,他可以嵌套使用,在@autoreleasepool中也可以做到:
@autoreleasepool{
@autoreleasepool{
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
}
}
不论在arc还是非arc中,我们可以使用调试用的非公开函数:_objc_autoreleasePoolPrint() 函数。利用它可以有效的帮我们调试注册到autoreleasepool上的对象。
__strong和__weak修饰符的变量类似与c++中的职能指针std::shared_ptr和std::weak_ptr。 shared_ptr通过引用计数来持有c++类实例,weak_ptr可避免循环引用。 在不得不使用没有__strong和__weak修饰符的c++时,强烈推荐这两种指针。
ARC新规则:
1、不能使用retain/release/retainCount/autorelease
2、不能使用NSAllocateObject/NSDeallocateObject
3、必须遵守内存管理的方法命名规则
4、不要显式调用dealloc
5、使用@autoreleasepool块替代NSAutoreleasePool
6、不能使用区域NSZone
7、对象型变量不能作为c语言结构体的成员
8、显式转换id和void*
官方文档说: “设置arc有效时,无需再次键入retain和release代码。”
不知道还记得说过的alloc的实现没,在gnustep中,alloc是通过调用NSAllocateObject来实现的。 其实他跟retain是一样的,这样在arc状态下就会引起错误。NSDeallocateObject也是同样的道理。
对象型变量不能作为c语言结构体的成员:
这是什么意思呢? 因为c语言不能管理结构体成员的生存周期。而arc却把内存管理交给了编译器, 所以编译器要知道并管理对象的生存周期。 所以不能作为结构体成员。
如果你很想把对象型变量加入到结构体成员中,你需要强制转换为void* 或者加上__unsafe_unretained修饰符。
显式转换id和void*
单纯赋值的情况: 使用__bridge 转换
id obj = ------
void *p = (__bridge void*)obj;
id o = (__bridge id)p;
这样赋值 有个缺点,就是安全性问题,他会比__unsafe_unretained安全性更低,如果不注意管理对象,很容易造成内存泄露。
__bridge有两种类型,分别是:__bridge_retianed 和 __bridge_transfer。 他们类似于 retian和release;
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)obj;
}
//此时作用域结束,obj释放。 但是由于__bridge_retianed转换使变量p看上与处于持有对象的状态。所以对象不会被丢弃。
//此时作用域结束,obj释放。 但是由于__bridge_retianed转换使变量p看上与处于持有对象的状态。所以对象不会被丢弃。
id obj = (__bridge_transfer id)p;
//ob持有对象, p被释放。
在写代码的时候,当arc有效的时候,oc类的属性也会发生变化。
如:@property (nonatomic, strong) NSString *name;
下面是属性列表:
上面的copy属性,它的赋值是通过NSCopying接口和copyWithZone方法复制赋值源所生成的对象。
这里的nonatomic来解释一下: 他是禁止多线程,可以进行变量保护, 能提高效率。
还有一个 atomic 这是oc的线程保护机制,防止发生信息未写完而被读取。
这里不得不说一下内存分配的事情:
NSObject * __strong *array = nil;
如下分配:
array = (id __strong *)calloc(***, sizeof(id));
这里是分配的***个所需要的内存块,由于我们使用了__strong修饰符, 所以必须先初始化为nil; calloc会给你的区域初始化为0;
但是我们使用malloc会怎样呢 ?
array = (id __strong*)malloc(sizeof(id) * ***);
这里是没有初始化的,也就是说这里的内存是随机分配的。
如果你这样来初始化:
for (NSInteger i = 0; i < ***; ++i) {
array[i] = nil;
}
这样很危险,因为我们是强引用对象, 当你nil赋值的时候,array[i]持有的对象会被释放掉,但是此时的对象时随机分配的地址,是无意义的,也就是说我们释放了一个不存在的对象。
此时我们需要使用memset来初始化。
memset(array, 0, *** * sizeof(id));
ARC的实现:
1、__strong
最优化问题。
id __strong obj = [NSMutableArray array];
他在编译器中会出现模拟代码 如下:
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedValue(obj);
objc_release(obj);
这里有个函数 objc_retainAutoreleasedValue。 还存在一个函数 objc_autoreleasepoolReturnValue。
看一下array函数的转换:
+(id)array {
return [[NSMutableArray alloc]init];
}
转换模拟代码:
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
objc_autoreleaseReturnValue函数返回注册对象到autoreleasepool中。
objc_autoreleaseReturnValue会检查使用该函数的方法或者函数调用方的执行命令列表,如果方法或者函数的调用方在调用了方法或函数后紧接着调用objc_retainAutorelwasedReturnValue函数, 那么将不会将返回的对象注册到autoreleasepool中,而是直接传递到方法或者函数的调用方。
如图协作:
2、__weak
1)、若附有__weak修饰符的变量所引用的对象被抛弃,则将nil赋值给该变量。
2)、若用附有__weak修饰符的变量,即是试用注册到autoreleasepool中的对象。
id __weak obj1 = obj;
他做了什么呢?
来看:id obj1;
objc_initWeak(&obj, obj);
objc_destroyWeak(&obj1);
这里就是初始化和结束的过程。
其实就像这样:
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
解释一下: 这里第一个storeWeak函数把第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中。
可以注意到,第二次调用的时候第二个参数为0, 也就是说第二个参数为-0的时候,会把变量的地址从weak表中删除。
weak表是什么呢? 此时联想到引用计数表, 他们都是利用散列来实现的。 这里的一个键值可以注册多个变量的地址。 就跟一个对象可以同时付给多个附有__weak修饰符的变量。 也就是说,如果你用一个废弃对象的地址作为键值来检索,你能够告诉的获取对应的附有__weak修饰符的变量的地址。
当释放对象的时候, 废弃掉谁都不持有的对象,程序后续还会出现动作:
1、objc_release
2、因为引用计数为0, 所以执行dealloc
3、_objc_rootDealloc
4、object_dispose
5、objc_destructInstance
6、objc_clear_deallocating
最后的第六个步骤的函数会出现如下动作:
1、从weak表中获取废弃对象的地址为键值的记录。
2、将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil。
3、从weak表中删除该记录。
4、从引用计数表中删除废弃对象的地址为键值的记录。
当我们使用注册到autoreleasepool中的对象的时候:
id __weak obj1 = obj;
此时的模拟代码为:
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
objc_destroyWeak(&obj1);
这里增加了objc_loadWeakRetained和objc_autorelease的调用。
1、objc_loadWeakRetained:函数取出附有__weak修饰符变量所引用的对象,并retain。
2、objc_autorelease函数将对象注册到autoreleasepool中。
这样就可以安全的使用附有__weak修饰符的变量了。
但是如果对象很多呢 ?
最好的方法就是先暂时赋值给 __strong修饰符的变量。 这样对象就仅登录到autoreleasepool中一次。
最后提醒的是:id __weak obj = [[NSObject alloc] init]; 和 id __unsafe_unretained obj = [[NSObject alloc] init];
这样是不可以的, 前者是因为不能持有对象,后者是obj被赋予的是 悬垂指针。 虽然在arc中不会造成内存泄露,但是还是不要这样使用的好。
3、引用计数
获取引用计数值的函数: uintptr_t _objc_rootRetainCount(id obj) 这个函数可以获取指定对象的引用计数值(ARC中,retainCount已经不能用了);
函数 _objc_autoreleasePoolPrint函数会观察注册到autoreleasepool中的引用对象。