iOS学习笔记之ARC内存管理
写在前面
ARC(Automatic Reference Counting),自动引用计数,是iOS中采用的一种内存管理方式。
指针变量与对象所有权
指针变量暗含了对其所指向对象的所有权
- 当某个方法(或函数)有一个指向某个对象的局部变量时,可以称该方法(或函数)拥有该变量所指向的对象,如:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSString *test = [[NSString alloc] init];
}
return 0;
}
上述代码中,main函数中有一个指向NSString的test对象,因此main函数拥有那个用相应的NSString对象。
- 当某个对象有一个指向其他对象的实例变量时,可以称该对象用用该实例变量所指向的对象。如:
@interface Item : NSObject
{
NSString *_itemName;
}
@end
上述代码中,我们声明一个Item类,在其头文件中创建一个NSString类型的实例变量_itemName,因此Item类型的对象拥有一个NSString对象。
对象所有权用于帮助我们决定是否应该释放某个对象并回收该对象占有的内存
- 如果某个对象没有拥有者,就应该将其释放掉。没有拥有者的对象是孤立的,程序无法向其发送消息。保留这样的对象只会浪费宝贵的内存空间,导致内存泄漏问题。
- 如果某个对象有一个或多个拥有者,就必须保留不能释放。如果释放了某个对象,但是其他对象或方法仍然有指向该对象的指针(准确地说,是指向该对象被释放前的地址),那么向该指针指向的对象发送消息就会使应用崩溃。释放正在使用的对象的错误称为过早释放。指向不存在的对象的指针称为空指针或者空引用。
对象失去拥有者的情况 - 当程序修改某个指向特定对象的变量并将其指向另一个对象时。
- 当程序将某个指向特定对象的变量设置为nil时。
- 当程序释放对象的某个拥有者时。
- 当从collection类中(例如数组)删除对象时。
所有权链条
因为对象可以拥有其他对象,后者也可以再拥有别的对象,所以释放一个对象可能会产生连锁反应,导致多个对象失去拥有者,进而释放对象并归还内存。
强引用与弱引用
强引用:如果指针变量指向了某个对象,那么相应的对象就多一个拥有者,并且不会被程序释放,这种指针特性称为强引用。
弱引用:指针变量不影响其指向对象的拥有者个数。这种不会改变对象拥有者个数的指针特性称为弱引用。
弱引用非常适合解决强引用循环的内存管理问题。当两个或两个以上的对象相互之间有强引用特性的指针关联时,就会产生强引用循环。强引用循环会导致内存泄漏。当两个对象互相拥有时,将无法通过ARC机制来释放。即使应用中的其他对象都释放了针对这两个对象的所有权,这两个对象及其拥有的对象也无法释放。
属性特性
iOS中属性后可以跟一组特性,用于描述相应存取方法的行为。这些特性写在@property指令后的小括号中。如:
@property (nonatomic, readwrite, copy) NSString *itemName;
其中,第一个特性用于描述属性的多线程特性,第二个特性用于描述属性的读/写特性,第三个特性用于描述属性的内存管理特性。
属性的内存管理特性有四种可选类型:strong、weak、copy
和unsafe_unretained
。
unsafe_unretained
对于不指向任何对象的属性(如int value),不需要做内存管理,这时应该选用unsafe_unretained,它表示存取方法会直接为实例变量赋值。unsafe_unretained中的”unsafe(不安全)”是相对于弱引用而言的。与弱引用不同,unsafe_unretained类型的指针指向的对象被销毁时,指针不会自动设置为nil,而是成为空指针,因此不安全。但是当处理非对象属性时,是不会出现空指针问题的。unsafe_unretained是非对象属性的默认内存管理特性,不用明确写出。
对于指向oc对象的属性,四种类型都有可能,默认是strong类型,但通常需要明确写出(Apple可能会修改默认值)。
strong
在ARC环境下,某个对象只要有一个指向它的strong指针,该对象就不能被销毁。一旦该对象没有strong类型的指针指向它了,该对象就应该被销毁。某个对象的引用计数等于指向该对象的strong指针的个数。
weak
在ARC环境下,weak指针不会增加其指向对象的引用计数。当其指向的对象销毁后,weak指针自动设置为nil,避免了出现野指针(指向不可以内存的指针)
copy
copy略微复杂一些。分为浅拷贝和深拷贝。
浅拷贝
也称为指针拷贝,副本和源对象是同一个对象,源对象的引用计数加1,其本质是增加了一个指向源对象的指针。
深拷贝
副本对象和源对象是不同的两个对象,源对象引用计数器不变,副本对象计数器为1。其本质是产生了新的对象。
此外,复制还有copy与mutableCopy之分
copy
能接收copy消息的类必须遵守NSCopying协议。对系统的非容器类对象,copy是浅拷贝
对系统的容器类,如果是不可变容器,copy是浅拷贝
对系统的容器类,如果是可变容器,则copy是深拷贝,但其返回的是不可变对象。
mutableCopy
能接收mutableCopy消息的类必须遵守NSMutableCopying协议。对所有类,mutableCopy都是深拷贝。
首先看下系统非容器类的案例代码:
NSString *str0 = [NSString stringWithFormat:@"test"];
NSString *str1 = [str0 copy];
NSString *str2 = [str0 mutableCopy];
NSLog(@"The address of str0 is: %p", str0);
NSLog(@"The address of str1 is: %p", str1);
NSLog(@"The address of str2 is: %p", str2);
运行结果如下:
可见用copy的str1与str0的地址相同,而用mutableCopy生成的str2却与前面两者地址不同。
下面是一个NSArray类型的案例
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//声明一个不可变数组,并初始化
NSArray *array0 = [NSArray arrayWithObjects:@"one", @"two", nil];
//用copy复制一个NSArray副本,浅拷贝
NSArray *array1 = [array0 copy];
//用mutableCopy复制一个NSArray副本,深拷贝
NSArray *array2 = [array0 mutableCopy];
//用copy复制一个NSMutableArray副本,浅拷贝
NSMutableArray *array3 = [array0 copy];
//[array2 addObject:@"three"];//可通过编译,但运行异常
//用mutableCopy复制一个NSMutableArray副本,深拷贝
NSMutableArray *array4 = [array0 mutableCopy];
[array4 addObject:@"three"];//编译和运行都可通过
NSLog(@"The address of array0 is: %p", array0);
NSLog(@"The address of array1 is: %p", array1);
NSLog(@"The address of array2 is: %p", array2);
NSLog(@"The address of array3 is: %p", array3);
NSLog(@"The address of array4 is: %p", array4);
}
return 0;
}
运行结果如下
可见,使用mutableCopy的array2、array4的地址与array0地址不同,其他用copy得到的地址都相同。
如果想要自定义的对象拥有复制特性,必须实现NSCopying,NSMutableCopying协议,实现该协议的copyWithZone方法和mutableCopyWithZone方法。深拷贝和浅拷贝的区别就在于copyWithZone方法的实现,
各种内存特性使用情况
strong:默认属性,除了NSString/block外的OC对象都使用
weak:各种UI控件(不绝对,某些控件也要使用strong)
copy:copy创建的是不可变副本(imutable),而mutableCopy创建的是可变副本(mutable)
总结
对于属性特性中的strong、weak等关键字,也有对应的局部变量版本。在局部变量前添加__strong就声明了一个强引用特性的局部变量。
另外,有了ARC之后,并不意味着程序员就完全不用手动管理内存了。ARC只能引用在OC对象上,如果涉及到底层的malloc()或者free(),ARC就无用武之地了,必须要程序员手动管理。此外,对于strong类型的指针,在不再使用之后,程序员也要手动设置为nil。