本文来自内存管理文档的整理
在Objective-C中内存管理是基于引用计数的,所谓的引用计数就是每个对象都会有一个引用计数记录这个对象跟谁有联系,当这个引用计数为0的时候,对象就会释放.
在Cocoa框架下内存管理的策略:
1.你自己通过new alloc,copy,mutableCopy创建的对象,则自己拥有这个对象的所有权.
2.你可以使用retain获取对象的所有权,即对其引用计数+1.通过这个方法延长对象的声明周期,防止其提前释放掉
3.当你不需要这个对象的时候,记得使用release或autorelease放弃你对对象的所有权
4.你不能释放不是你拥有的对象的所有权.
ps: 使用非 new ,alloc,copy,mutableCopy创建的对象,如[NSString stringWithFormat:@""]则得到的对象会自动的被标记为autorelease.
MRR下实现set get 方法:
//set 方法
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
//get方法
- (NSNumber *)count {
return _count;
}
5.weak引用是一种非拥有的状态,使用weak修饰的变量并不会retain weak指向的对象.日常开发中有一下几种情况需要特殊注意:
delegate,Block,还有Timer. delegate和Block老生常谈这就细说了.
在NSTimer的文档中我们可以看到关于target的注解:
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
timer 会对target保持强引用直到timer调用invalidated方法.若是target也持有timer那么循环引用就会产生了
而且因为timer是一个循环的调用,会被加入到Runloop中,如果不调用invalidated,则timer会被Runloop一直强引用,直到Runloop退出.若是主线程的Runloop则会一直等到程序结束,timer都不会被释放
平常开发中记得一定要调用invalidated方法一般不会出现问题,但是某些特定的情况还是会出现没有合适的时机去调用invalidated,所以要避免循环引用.
目前有几种比较合理的解决方案:
第一中,写一个Timer的分类,让timer的target引用timer的类指针,这样避免产生循环引用
typedefvoid(^QSExecuteTimerBlock) (NSTimer*timer);
@interfaceNSTimer(QSTool)
+ (NSTimer*)qs_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(QSExecuteTimerBlock)block repeats:(BOOL)repeats;
@end
@implementationNSTimer(QSTool)
+ (NSTimer*)qs_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(QSExecuteTimerBlock)block repeats:(BOOL)repeats{
NSTimer*timer = [selfscheduledTimerWithTimeInterval:timeInterval target:selfselector:@selector(qs_executeTimer:) userInfo:[blockcopy] repeats:repeats];
return timer;
}+ (void)qs_executeTimer:(NSTimer*)timer{
QSExecuteTimerBlock block = timer.userInfo;
if(block) { block(timer); }
}
@end
这个方案比较简洁,好用比较推荐使用,另外这段代码要感谢@南华coder.
第二种方法就是通过一个代理,让代理去弱引用真的target,而timer去持有代理,这样就避免了循环引用了.下面是代理的实现:
@implementation BTWeakTimerTarget
{
__weak target;
SEL selector;
}
[...]
- (void)timerDidFire:(NSTimer *)timer{
if(target) {
[target performSelector:selector withObject:timer];
} else {
[timer invalidate];
}}
@end
然后,你使用的时候
BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)];
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...];
ps:在Cocoa框架内Notification 和 KVO 都是weak 引用
NSNotificationCenter 会若引用被监听的对象
Neither the object receiving this message, nor observer, are retained
这段话是KVO的官方文档的解释
KVO 中无论是观察者还是被观察者,两者都是若引用
6.不要在dealloc内部做一些有关资源的操作
7.集合类型当你把一个对象加入其中,会被自动retain,当你把对象从集合中移除则会被自动realease
8.不要依赖引用计数去判断内存是否泄漏这些问题,因为引用计数并不准确,有可能有错误.
接下来就会介绍比较复杂的Autorelease
在NSObjec.mm文件可以看到Autorelease的实现,代码太多不放上来,我们看一下注释
/*
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
*/
简单翻译一下就是:
每个线程的 autoreleasepool 是一个储存指针的栈
每个指针都代表一个要释放的对象或者是 autoreleasepool的边界
一个 pool token 是一个指向POOL_BOUNDARY的指针,当pool开始pop的时候,每个哨兵对象后面的对象都会释放
这个栈是一个以page为节点的双向链表
Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。
基本可以得到一些信息,Autorelease pool 其实就是一个栈,内部存储自动释放的对象,当达到一个临界点的时候就会释放.这个临界点我们可以在官方文档中看到
At the end of the autorelease pool block, objects that received an autoreleasemessage within the block are sent a release message—an object receives a release message for each time it was sent an autorelease message within the block.
意思就是当自动释放的对象在超出自动释放池的作用域后开始准备释放
那么什么时候需要自己创建自动释放池呢
1.当你写一个不是基于UI Framework的程序,比如命令行工具
2.当你写一个循环创建了大量的临时变量
3.当你创建了子线程的时候
写到这,可能大家会说平常开发,出现子线程并没有创建Autorelease Pool,也没有内存泄漏.
这个问题我也是很疑惑,不过看了官方文档的描述和@Joy___的文章之后,大概知道了原因了,
官方文档中有描述到,每个线程都会有自己的Autorelease Pool栈,在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性。并且苹果没有对应的官方文档阐述此事,但是你可以通过源码了解。
以上就是对Object-c的内存理解