iOS之内存管理
Tagged Pointer
- bit64之后引入,针对小对象(NSString、NSNumber、NSDate等)的存储
- 使用前:
- NSNumber等对象需要动态分配内存,维护引用计数,NSNumber指针存储的是堆中的NSNumber对象的地址值
- 使用后:
- NSNumber指针里面存储的数据变成了Tag + Data,将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据(堆空间)
- 判断是否是Tagged Pointer:
- 对于Mac平台,指针的二进制最低有效位是
1
时,该指针为Tagged Pointer - 对于iOS平台,指针的二进制最高有效位是
1
时,该指针为Tagged Pointer
- 对于Mac平台,指针的二进制最低有效位是
objc_msgSend
能识别Tagged Pointer,直接从指针中提取数据,节省了调用开销(不必在类对象的方法列表中查找方法)- 优点:存储数据节省了内存开销,提取数据节省了调用开销
内存管理策略
MRC:assign、copy、retain
ARC:assign、copy、strong、weak
assign
:- 适用于基本数据类型
- 实现方式是直接赋值
1
2
3
4
5@property(nonatomic, assgin) int age;
// MRC下或ARC编译后的实现
-(void)setAge:(int)newAge {
_age = newAge;
}copy
:- 适用于
block
和NSString
、NSArray
、NSDictionary
、NSSet
等Foundation框架的不可变类型。 - 实现方式是对源对象进行一份浅拷贝(指针拷贝),指向源对象,所以源对象的引用计数会+1
1
2
3
4
5
6
7
8@property(nonatomic, copy) NSArray *array;
// MRC下或ARC编译后的实现
-(void)setArray:(NSArray *)newArray {
if (_array != newArray) {
[_array release];
_array = [newArray copy];
}
}- 适用于
retain
/strong
:- 适用于对象类型和
NSMutableString
、NSMutableArray
、NSMutableDictionary
、NSMutableSet
等Foundation框架的可变类型。 - 实现方式是对源对象进行
retain
,源对象的引用计数+1
1
2
3
4
5
6
7@property(nonatomic, retain) NSMutableArray *array;
// MRC下或ARC编译后的实现
-(void)setArray:(NSMutableArray *)newArray {
if (_array != newArray) {
[_array release];
_array = [newArray retain];
}可以看到,
copy
和retain
的实现上就是对源对象进行copy还是retain操作的差别。上述如果没有新旧对象是否相同的判断,多次调用
setter
传入同一个源对象时,可能会造成源对象retainCount
变成0,导致对源对象retain报错。比如:MRC下,ZZPerson类中有个_dog成员和setDog方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// ZZPerson拥有dog属性
@interface ZZPerson: NSObject
{
ZZDog *_dog;
}
-(void)setDog(ZZDog *)dog;
@end
@implementation ZZPerson()
-(void)setDog:(ZZDog *)dog {
[_dog release];
_dog = [dog retain];
}
-(void)dealloc {
[_dog release];
[super dealloc];
}
@end外部调用时:
1
2
3
4
5
6
7
8
9
10
11{
ZZDog *dog = [[ZZDog alloc] init];
ZZPerson *person = [[ZZPerson alloc] init];
[person setDog: dog]; // dog引用计数为2
[dog release]; // dog引用计数为1(person引用)
[person setDog: dog]; // 这里set时先release再retain,会出错
[person setDog: dog];
[person release];
}上面第二次setDog方法调用拆解:
[_dog release]
: _dog指向dog,dog被release
,引用计数变成0_dog = [dog retain]
: dog已被释放,retain
则会报错- 适用于对象类型和
weak:
适用于delegate,以及一些需要弱引用的特殊场景,比如通过代理对象弱持有target来解决Timer循环引用、IBOutlet控件自身已经对该对象强引用而没必要再使用strong
底层实现:
runtime在类对象中维护了一个weak表,用于存储指向某个对象的所有weak指针。这个表是一个由单个自旋锁管理的哈希表;
对象准备释放时,调用
clearDeallocating
函数。clearDeallocating
函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
__unsafe_unretained
- 不安全
- 当指向的对象被销毁后,指针不会自动置为nil,仍然指向原来那块内存,形成悬垂指针。
atomic
- 可以保证属性的
setter
和getter
都是原子性操作,相当于setter
和getter
内部加了线程同步的锁 - 不能保证使用属性的过程是线程安全的
比如:
1
2
3
4
5
6
7
8
9@property(atomic, strong) NSMuableArray *array;
{
self.array = [[NSMutableArray alloc] init];
// 只能保证[self array]是线程安全的,但不能保证addObject是线程安全的
[self.array addObject: @1];
[self.array addObject: @2];
[self.array addObject: @3];
}- 可以保证属性的
copy和mutableCopy
copy的目的:得到一份源对象的副本,源对象和副本互不影响;
深拷贝和浅拷贝:
- 对不可变的源对象:copy时是浅拷贝(指针拷贝),mutableCopy是深拷贝;
- 对可变对象:copy和mutableCopy都是深拷贝;
拷贝结果:
- copy的结果都是不可变副本对象;
- mutableCopy的结果都是可变副本对象。