1.下面代码执行结果如何
// Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSMutableArray *data;
@end
// 调用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
p.data = [NSMutableArray array];
[p.data addObject:@"jack"];
[p.data addObject:@"rose"];
NSLog(@"end");
}
运行结果

- (void)setData:(NSArray *)data {
if (_data != data) {
[_data release];
_data = [data copy];
}
}
分析:因为
data是copy属性,所以在其set方法里先执行判断,然后执行release操作,最后执行copy操作,变成了一个不可变对象。
二 copy
- 拷贝的目的:产生一个副本对象,跟源对象互不影响
- 修改了源对象,不会影响副本对象
- 修改了副本对象,不会影响源对象
iOS提供了2个拷贝方法
-
copy不可变拷贝,产生不可变副本 -
mutableCopy可变拷贝,产生可变副本
深拷贝和浅拷贝
-
深拷贝内容拷贝,产生新的对象 -
浅拷贝指针拷贝,没有产生新的对象
copy和mutableCopy 图解

1.
copy都是不可变拷贝,产生不可变副本。mutableCopy都是可变拷贝,产生可变副本。
2.除了不可变对象的copy是浅拷贝,其他都是深拷贝。
三 引用计数的存储
在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中

-
refcnts是一个存放着对象引用计数的散列表
四 weak实现原理 - dealloc
- 当一个对象要释放时,会自动调用
dealloc,接下的调用轨迹是
1.dealloc
2._objc_rootDealloc
3.rootDealloc
4.object_dispose
5.objc_destructInstance、free

五 自动释放池
自动释放池的主要底层数据结构是:
__AtAutoreleasePool、AutoreleasePoolPage调用了
autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的__AtAutoreleasePool结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
下面将代码进行转换
@autoreleasepool {
Person *p4 = [[[MJPerson alloc] init] autorelease];
}
将上述代码转成C++代码
{
__AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
去除一些不必要的代码后变成下面这个样子
{
__AtAutoreleasePool __autoreleasepool;
MJPerson *person = [[[MJPerson alloc] init] autorelease];
}
又因为__AtAutoreleasePool是一个结构体,所以创建时会调用其构造函数__AtAutoreleasePool(),当离开其作用域后,会调用其析构函数~__AtAutoreleasePool(),所以上面的代码又可以转换成下面的代码
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
5.0 自动释放池
- 自动释放池的主要底层数据结构是:
__AtAutoreleasePool、AutoreleasePoolPage - 调用了
autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
源码分析
- clang重写
@autoreleasepool - objc4源码:
NSObject.mm

变量说明
-
magic用来校验 AutoreleasePoolPage 的结构是否完整 -
next指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() -
thread指向当前线程 -
parent指向父结点,第一个结点的 parent 值为 nil -
child指向子结点,最后一个结点的 child 值为 nil -
depth代表深度,从 0 开始,往后递增 1 -
hiwat代表 high water mark
5.1 AutoreleasePoolPage的结构
- 每个
AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址 - 所有的
AutoreleasePoolPage对象通过双向链表的形式连接在一起

atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
上图的执行步骤说明
- 调用
push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址,即返回给atautoreleasepoolobj。 - 调用
pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY -
id *next指向了下一个能存放autorelease对象地址的区域
代码例子如下
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
Person *p1 = [[[Person alloc] init] autorelease];
Person *p2 = [[[Person alloc] init] autorelease];
@autoreleasepool { // r2 = push()
for (int i = 0; i < 5; i++) {
Person *p3 = [[[Person alloc] init] autorelease];
}
@autoreleasepool { // r3 = push()
Person *p4 = [[[Person alloc] init] autorelease];
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
} // pop(r1)
return 0;
}
执行结果

- 因为只打印了一个
PAGE,所以说明他们是在同一个AutoreleasePoolPage,只是每次一个新的autoreleasepool,都会插入一个POOL_BOUNDARY。- 每次释放对象时,都是从后往前释放,直到遇到
POOL_BOUNDARY为止。
代码例子二
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
@autoreleasepool {
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
return 0;
}
}
执行结果

代码例子三
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
@autoreleasepool { // r2 = push()
for (int i = 0; i < 600; i++) {
MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
}
@autoreleasepool { // r3 = push()
MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
} // pop(r1)
return 0;
}
执行结果



5.2 Runloop和Autorelease
iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了
kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()第2个Observer
<1> 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
<2> 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
5.3 autorelease对象在什么时机会被调用release
实践内容可以参考 你真的懂iOS的autorelease吗?
代码例子如下
- MRC环境下
- (void)viewDidLoad {
[super viewDidLoad];
// 这个Person什么时候调用release,是由RunLoop来控制的
// 它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release
MJPerson *person = [[[MJPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
运行结果如下

- 得出结论,
autorelease并不是根据对象的作用域来决定释放时机。- 实际上,
autorelease释放对象的依据是Runloop,简单说,runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration。- 本次runloop迭代休眠之前调用了
objc_autoreleasePoolPop()方法,然后调用release,从而释放Person对象。
5.4 方法里有局部对象, 出了方法后会立即释放吗
- ARC环境下
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}

通过打印结果可知,当
person对象出了其作用域后就销毁,即系统会在它出作用域的时候,自动调用其release方法。
扩展
既然由runloop来决定对象释放时机而不是作用域,那么,在一个{}内使用循环大量创建对象就有可能带来内存上的问题,大量对象会被创建而没有及时释放,这时候就需要靠我们人工的干预autorelease的释放了。
上文有提到autorelease pool,一旦一个对象被autorelease,则该对象会被放到iOS的一个池:autorelease pool,其实这个pool本质上是一个stack,扔到pool中的对象等价于入栈。我们把需要及时释放掉的代码块放入我们生成的autorelease pool中,结束后清空这个自定义的pool,主动地让pool清空掉,从而达到及时释放内存的目的。优化代码如下
@autoreleasePool{
//domeSomeThing;
}
什么时候用@autoreleasepool
根据 Apple的文档 ,使用场景如下:
- 写基于命令行的的程序时,就是没有UI框架,如
AppKit等Cocoa框架时。 - 写循环,循环里面包含了大量临时创建的对象。(本文的例子)
- 创建了新的线程。(非
Cocoa程序创建线程时才需要) - 长时间在后台运行的任务。
autorelease 机制基于UI framework。因此写非UI framework的程序时,需要自己管理对象生存周期。autorelease触发时机发生在下一次runloop的时候。因此如何在一个大的循环里不断创建autorelease对象,那么这些对象在下一次runloop回来之前将没有机会被释放,可能会耗尽内存。这种情况下,可以在循环内部显式使用@autoreleasepool {}将autorelease对象释放。- 自己创建的线程。
Cocoa的应用都会维护自己autoreleasepool。因此,代码里spawn的线程,需要显式添加autoreleasepool。注意:如果是使用POSIX API创建线程,而不是NSThread,那么不能使用Cocoa,因为Cocoa只能在多线程(multithreading)状态下工作。但可以使用NSThread创建一个马上销毁的线程,使得Cocoa进入multithreading状态。
上述结论来自 guijiewan 的CSDN 博客 ,什么时候应该使用Autorelease
什么对象会加入Autoreleasepool中
- 使用
alloc、new、copy、mutableCopy的方法进行初始化时,由系统管理对象,在适当的位置release。 - 使用
array会自动将返回值的对象注册到Autoreleasepool。 -
__weak修饰的对象,为了保证在引用时不被废弃,会注册到Autoreleasepool中。 -
id的指针或对象的指针,在没有显示指定时会被注册到Autoleasepool中。
本文参考MJ底层原理教程,非常感谢
本文参考Autorelease Pool学习笔记,非常感谢。
优秀文章推荐 - Objective-C Autorelease Pool 的实现原理
- 多多点赞,打赏更好,您的支持是我写作的动力。
项目连接地址 - MemoryManage-CADisplayLink+Timer
项目连接地址 - MemoryManage-Copy
项目连接地址 - MemoryManager_autorelease1
项目连接地址 - MemoryManager_autorelease
