这篇文章其实是深入内存管理:从所有权修饰符开始的补充。因为由于__autoreleasing
的试验过于多,都写在上一篇文章中会使得文章篇幅结构很难看,所以在这里新建一篇文章来记录。
方法介绍
下面需要介绍两个方法:
1._objc_rootRetainCount(id obj)
方法,作用是返回obj的引用计数。
2._objc_autoreleasePoolPrint()
方法,作用是打印当前的自动释放池对象。
使用方法:
直接定义在类中就可以使用
extern uintptr_t _objc_rootRetainCount(id obj);
extern void _objc_autoreleasePoolPrint(void);
试验开始
一、基础试验
下面有三个小实验,为了证明结论:
在取得非自己生成并持有的对象时,编译器会默认把对象注册到自动释放池中。
也就是说:
编译器为判断方法名是否是以
alloc/new/copy/mutableCopy
开头,如果不是,就自动将返回的对象注册到池子中。
1. 直接alloc方法初始化
代码:
id __weak obj0;
{
id obj1 = [[NSMutableArray alloc] init];
obj0 = obj1;
NSLog(@"%p", obj0);
NSLog(@"%lu", _objc_rootRetainCount(obj0));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-1-%@", obj0);
实验结果:
程序到最后一行时输出为空。
输出:
分析:
这里其实很简单,编译器的模拟代码为
id obj1 = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj1, @selector(init));
objc_release(obj1);
当变量obj1的作用域消失时,编译器会自动插入objc_release(obj1);
。这里并没有涉及到自动释放池。所以array对象会被自动销毁。
2. 用array方法初始化
代码
id __weak obj0;
{
id obj2 = [NSMutableArray array];
obj0 = obj2;
NSLog(@"%p", obj2);
NSLog(@"%lu", _objc_rootRetainCount(obj2));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-2-%@", obj0);
实验结果:
分析:
这里发现obj2的引用计数为2,再看自动释放池最后一个对象类型为__NSArrayM
,正是obj2持有的对象。说明该对象已经加入到了自动释放池中,所以最后输出有值。那么这个对象什么时候释放呢,我们后面说。
3. 用array方法初始化 并自己添加池子
代码:
@autoreleasepool {
id obj3 = [NSMutableArray array];
obj0 = obj3;
NSLog(@"%p", obj3);
NSLog(@"%lu", _objc_rootRetainCount(obj3));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-3-%@", obj0);
实验结果:
分析:
虽然这里和实验2一样被放入到了池子中,但是最后打印还是为空。说明在退出池子后,对象被销毁了。
4.总结
以上三个实验验证了之前提出的结论。
下面以第三个实验作为例子分析代码:
@autoreleasepool {
// [NSMutableArray array]返回的对象会被默认加入到池子中
// 对象的引用计数为1
// obj3默认为__strong,强引用array对象
// array对象的引用计数为2
id obj3 = [NSMutableArray array];
// obj0对__weak 因此对象引用计数不变
obj0 = obj3;
NSLog(@"%p", obj3);
NSLog(@"%lu", _objc_rootRetainCount(obj3));
_objc_autoreleasePoolPrint();
}
// obj3的作用域结束,释放对象 计数-1
// 池子结束 池子中的对象要被释放 计数-1
// 对象计数为0 因此销毁
// 输出为空
NSLog(@"obj0-3-%@", obj0);
二、再次验证试验一
下面验证自己定义的方法是否也可以遵守以上结论。
1.不以alloc等开头的方法
代码:
{
id obj5 = [[self class] Object];
obj0 = obj5;
NSLog(@"%p", obj0);
NSLog(@"%lu", _objc_rootRetainCount(obj0));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-5-%@", obj0);
+ (id)Object
{
id array = [[NSMutableArray alloc] init];
return array;
}
输出:
分析:
最后输出没有问题,确实是加入到了池子中。但是为什么这里的计数是3呢???
2.以alloc等开头的方法
代码:
{
id obj6 = [[self class] allocObject];
obj0 = obj6;
NSLog(@"%p", obj0);
NSLog(@"%lu", _objc_rootRetainCount(obj0));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-6-%@", obj0);
+ (id)allocObject
{
id array = [[NSMutableArray alloc] init];
return array;
}
输出:
分析:
这里的引用计数变成2了,但是没有加入到池子中,且最后输出为空。说明这里多出的1的引用计数来自+ (id)allocObject
方法。
3.总结
自定义的方法仍然可以说明实验一的结论。
三、何时释放
下面定义一个属性@property (nonatomic, weak) id obj;
,并在- (void)viewDidLoad
中赋值。
- (void)viewDidLoad
{
[super viewDidLoad];
id obj = [NSMutableArray array];
self.obj = obj;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%p", self.obj);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%p", self.obj);
}
输出:
2017-04-28 11:23:54.829 MRCTest[57782:5898069] 0x6000000523f0
2017-04-28 11:23:54.832 MRCTest[57782:5898069] 0x0
为什么在viewWillAppear
中还存在而到了viewDidAppear
中就为空了?我猜测是否是执行完viewWillAppear
后,自动释放池销毁了,导致array对象也销毁了。
其实原因是viewDidLoad
和viewWillAppear
是在同一个RunLoop中调用的,而viewDidAppear
与他们不是同一个,因此当RunLoop一圈结束时,池子被销毁,里面的对象也自然被销毁了。
最后
其实我想写一点__strong
以及__autoreleasing
底层代码的,但是因为自己也没有完全理解,所以还是不乱写了。