原文链接Reference Counting Brief Analysis
Begin
这篇文不像动画那么有趣。记录的是我在学习Reference Counting中遇到的坑。倘若你之前只是听说过但是没有具体实践过,我想你在阅读本文后会有很大的收获。
Manual Reference Counting Introduce
笔者学习iOS开发一年有余,对于我的iOS入门书籍,是Objective-C Programming: The Big Nerd Ranch Guide,在23.4部分,对Referrence Counting会有介绍。我们对MRC最初的认识就是,retain计数加一,release计数减一,autorelease计数将来会减一,retainCount可以返回引用计数。当引用计数减到0时,系统将会自动调用对象的dealloc
方法,你可以把它类比成C#中的dispose
方法,而原先的开发人员可以在dealloc
中释放或清理资源。
也许第一遍看这些知识你正处于一个入门开发学习状态而没有进行试验,当我们深入去学习这门语言之后,你会发现很多问题。接下来,我们一一提出并解决。
alloc/retain/release/dealloc是如何实现的?
由于Cocoa framework的闭源,我们只能通过其互换框架GNUstep来了解其原理。首先我们通过alloc
方法入手。
1 2 3 4 5 6 7 |
+ (id) alloc { return [self allocWithZone: NSDefaultMallocZone()]; } + (id) allocWithZone: (NSZone *)z { return NSAllocateObject(self, 0, z); } |
alloc
方法会调用NSAllocateObject
函数。具体是做什么的呢?我们往后看。
1 2 3 4 5 6 7 8 9 10 11 12 |
struct obj_layout { NSUInteger retained; }; inline id NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone) { int size = /* 计算容纳对象所需要的内存大小 */ // 分配内存空间 id new = NSZoneMalloc(zone, size); // 空间数据置0 memset(new, 0, size); new = (id)&((struct obj_layout *) new)[1]; } |
NSAllocateObject
函数通过调用NSZoneMalloc
函数来分配存放对象所需的内存空间,之后将该内存空间置0,最后返回作为对象而使用的指针。那么NSZone
又是什么?之前我想让大家移至CocoaDev,但是现在作者不在维护了。这里所说的NSZone
我做了一下翻译:从大体上来说,NSZone
是Apple分配和释放内存的一种方式,它不是一个对象,而是使用C语言中的结构体来存储关于对象的内存管理信息。基本上,不需开发者去管理它,Cocoa Application使用一个默认的NSZone
来对应用的对象进行管理。但是当默认的NSZone
里面管理了大量数据的时候,你会想要一个自己控制的NSZone
。中重视和,大量对象的释放可能会导致严重的内存碎片化,Cocoa本身有做过优化,每次alloc
时会尝试着填满内存空隙,但如此开销会很大。于是,为了优化效率,你可以自己创建NSZone
,当你有大量的alloc
请求时,就全部转移到指定的NSZone
,便可减少大量的时间开销。而且,使用NSZone
还可以一次性的将你创建在NSZone
的东西全部清除,避免逐个dealloc
。
熟悉C或者C++的读者,读过以后可以立马反应到,这其实就是一个官方封装的内存池。无论是优化内存碎片化还是对象统一释放,都是内存池的显著特点。总的来说,当你需要大量创建对象的时候,使用NSZone
能提高效率的。在Cocoabuilder中,有一篇叫what’s an NSZone?的帖子中,Timothy J. Wood写道:由于历史原因,现在已经不能创建一个真正的NSZone
,而是在Main Zone中创建一个Child Zone,这样不会使存储单元过度碎片化。发表日期是2002年,也就是说,Cocoa很早之前就已经注意到内存碎片的危险,而改善了Zone方法。
再来说一下retain
和release
。我们也从GNUstep源码入手:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (id) retain { NSIncrementExtraRefCount(self); return self; } inline void NSIncrementExtraRefCount(id anObject) { // 判断计数最大值 if (((struct obj_layout *)anObject)[-1].retained == UINT_MAX - 1) { [NSException raise: NSInternalInconsistencyException format: @"NSIncrementExtraRefCount() asked to increment too far"]; } ((struct obj_layout *)anObject)[-1].retained++; } |
大概扫一遍代码,其实我们只是对计数的上线做了一个判断。UINT_MAX - 1
这是个什么东西呢。直接敲到Xcode中发现这个值得18446744073709551615
,转换成二进制1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
,也就是我们常说的2^64 – 1。这个值也就是-1
在内存当中的补码存储形式。记住这个值,我们后面还会遇到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (void) release { if (NSDecrementExtraRefCountWasZero(self)) { [self dealloc]; } } bool NSDerementExtraRefCountWasZero(id anObject) { if (((strcut obj_layout *)anObject)[-1].retained == 0) { return YES; } else { ((struct obj_layout *) anObject)[-1].retained --; return NO; } ݆是没有具体实践过,我想你在阅读本文后会有很大的收获。
Manual Reference Counting Introduce笔者学习iOS开发一年有余,对于我的iOS入门书籍,是Objective-C Programming: The Big Nerd Ranch Guide,在23.4部分,对Referrence Counting会有介绍。我们对MRC最初的认识就是,retain计数加一,release计数减一,autorelease计数将来会减一,retainCount可以返回引用计数。当引用计数减到0时,系统将会自动调用对象的 也许第一遍看这些知识你正处于一个入门开发学习状态而没有进行试验,当我们深入去学习这门语言之后,你会发现很多问题。接下来,我们一一提出并解决。
由于Cocoa framework的闭源,我们只能通过其互换框架GNUstep来了解其原理。首先我们通过
NSAllocateObject 函数通过调用NSZoneMalloc 函数来分配存放对象所需的内存空间,之后将该内存空间置0,最后返回作为对象而使用的指针。那么NSZone 又是什么?之前我想让大家移至CocoaDev,但是现在作者不在维护了。这里所说的NSZone 我做了一下翻译:从大体上来说, 熟悉C或者C++的读者,读过以后可以立马反应到,这其实就是一个官方封装的内存池。无论是优化内存碎片化还是对象统一释放,都是内存池的显著特点。总的来说,当你需要大量创建对象的时候,使用 再来说一下
大概扫一遍代码,其实我们只是对计数的上线做了一个判断。
|