iOS之内存那些你不知道的事
首先来复习一下iOS的内存基本知识
内存引用计数的实现
GNUstep的实现是将引用计数保存在对象占用内存块头部的变量中
好处是:
- 少量的代码即可完成。
- 能够统一管理引用计数内存块和对象引用计数内存块
苹果的实现是保存在引用计数hash表中
好处是:
- 对象用内存块的分配无需考虑内存块的头部
- 引用计数表各记录中存有内存块地址,可以从各个记录追溯到各对象的内存块,这点对调试非常重要
weak对象释放是自动致nil实现:
也是通过一个weakhash
表实现的,将weak
的对象地址注册到weakhash
表中,如果该对象被destroy
销毁,则在weak
表中将该对象地址致nil,并清除记录
来自objective-c高级编程一书
ARC下的内存管理
ARC(Automatic Reference Counting)
,自动引用计数,实际就是编译时期自动在已有代码中插入合适的内存管理代码以及在Runtime
做一些优化。
自动插入合适的内存管理代码
- 如果是需要自己生成持有的对象,需要在对象结束的时候插入
release
代码 - 不是自己生成持有的对象会将对象加入
autoreleasePool
runtime
优化:
- 合并对称的引用计数操作。比如将
+1/-1/+1/-1
直接置为 0. - 巧妙地跳过某些情况下
autorelease
机制的调用。当返回值被返回之后,紧接着就需要被retain
的时候,没有必要进行autorelease
+retain
,直接什么都不要做就好了。
接下来我们来看一下有趣的现象
首先了解几个私有API
查看自动释放池的状态:
//声明
extern void _objc_autoreleasePoolPrint();
//调用 打印自动释放池里的对象
_objc_autoreleasePoolPrint();
在ARC下查看对象的引用计数
//先声明私有的API
uintptr_t _objc_rootRetainCount(id obj);
//调用 查看对象的引用计数
_objc_rootRetainCount(obj)
注意:这两个私有的API都是不一定准确的,亲测不准,所以要多测。
接下来重点
声明一个方法以new
这些字符开头,返回一个对象,结果也是有区别的。测试方法如下:
声明一个new开头的方法
+ (Person *)newPerson {
Person *person = [[Person alloc] init];
return person;
}
声明一个其他命名开头的方法
+ (Person *)getPerson {
Person *person = [[Person alloc] init];
return person;
}
测试:
- (void)doSomething {
Person *personOne = [Person newPerson];
// ...
Person *personTwo = [Person getPerson];
// ...
/*方法结束时, ARC会自动插入[personOne release].想想是为什么?*/
_objc_autoreleasePoolPrint();
/*结果personOne没有加入autoreleasePool,personTwo加入了autoreleasePool*/
NSLog(@"newPerson 0x%@", personOne);
NSLog(@"count: %zd",_objc_rootRetainCount(personOne));
NSLog(@"onePerson 0x%@", personTwo);
NSLog(@"count: %zd",_objc_rootRetainCount(personTwo));
/* personOne的引用计数1,personTwo的引用计数2*/
}
测试结果:
- 方法以
new
开始, 所以直接返回对象本身,系统不会加上autorelease
,而是在适当的时候直接release
- 方法不以
alloc/new/copy/mutableCopy
开始,所以返回的是[person autorelease]
结论:
ARC通过命名约定将内存管理规则标准化;在ARC下如果用alloc/new/copy/mutableCopy
方法开头的返回值取得对象,才能自己生成并持有对象, 其余情况均为”取得非自己生成并持有的对象”。有点晕是不是,其实就是如果用上述字段开头的方法会在适当的时候进行release
,其余的会加上autorelease
,在释放池中去释放。
各位可以自己去验证一下,哈哈
以上验证纯属个人娱乐,在实际项目中不建议自定义方法命名以alloc/new/copy/mutableCopy
字符串开头;