alloc/retain/release/dealloc实现
包罗NSObject类的Foundation框架的源代码没有公开,不外Foundation框架使用的Core Foundation框架的源代码以及通过调用NSObject类进行内存管理部分的源代码是公开的。但是没有NSObject类的源代码,就很难了解NSObject类的内部实现细节。为此,我们使用开源软件GNUstep来说明,
GNUstep是Cocoa框架的互换框架,也就是说在使用者看来,两者的行为和实现方式是一样的,或者说非常相似。
GNUstep源代码中NSObject类的alloc类方法是这样的:
- id obj = [NSOject alloc];
复制代码 在NSObject.m源代码中的实现如下:
- + (id)alloc {
- return [self allocWithZone:NSDefaultMallocZone()];
- }
-
- + (id)allocWithZone:(NSZone*)z {
- return NSAllocateObject(self, 0, z);
- }
复制代码 通过allocWithZone:类方法调用NSAllocateObject函数分配了对象,下面来看看NSAllocateObject函数:
- struct obj_layout {
- NSUInteger retained;
- }
-
- inline id
- NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone) {
- int size = 计算容纳对象所需内存大小;
- id new = NSZoneMalloc (zone, size);
- memset (new, 0, size);
- new = (id) & ((struct obj_layout *)new)[1];
- }
复制代码 NSAllocateObject函数通过调用NSZoneMalloc函数来分配存放对象所需的内存空间,之后将该内存空间置0,末了返回作为对象而使用的指针。
NSDefaultMallocZone、NSZoneMalloc等名称中包罗的NSZone,是为防止内存碎片化而引入的布局。对内存分配的区域自己进行多重化管理,根据使用对象的目标、对象的大小分配内存,从而进步内存管理的服从。
但是现在的运行时体系只是简朴地忽略了区域的概念,运行时体系中的内存管理自己已极具服从,使用区域来管理内存反而会引起内存使用服从低下以及源代码复杂化等标题。
以下是去掉NSZone后简化了的源代码:
- struct obj_layout {
- NSUInteger retained;
- };
- + (id)alloc {
- int size = sizeof (struct obj_layout) + 对象大小;
- struct obj_layout *p = (struct obj_layout*)calloc(1,size);
- return (id)(p+1);
- }
复制代码 alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置零后返回。
对象的引用计数可以通过retainCount实例方法取得。
- id obj = [[NSObject alloc] init];
- NSLog(@"retainCount = %d", [obj retainCount]);
- //显示retainCount = 1
复制代码 执行alloc后对象的retainCount是"1"。可以通过GNUstep的源代码来确认
- - (NSUInteger)retainCount {
- return NSExtraRefCount(self) + 1;
- }
- inline NSUInteger
- NSExtraRefCount (id anObject) {
- return ((struct obj_layout *)anObject)[-1].retained;
- }
复制代码 由对象寻址找到对象内存头部,从而访问其中的retained变量。
分配时全部置0,所以retained为0。再由NSExtraRefCount(self) + 1可得出,retainCount为1。不难推测出:retain方法使retained变量加1,而release方法使retained变量减1。
下面是retain的源代码
- - (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++;
- }
复制代码 虽然有当retained变量超出最大值时发生异常的代码,但实际上只运利用retained变量加一的retained++代码/同样,release实例方法进行retained--并在该引用计数变量为0时作出处理。下面通过源代码来确认。
- - (void)release {
- if (NSDecrementExtraRefCountWasZero(self))
- [self dealloc];
- }
- BOOL
- NSDecrementExtraRefCpuntWasZero (id anObject) {
- if (((struct obj_layout *)anObject)[-1].retained == 0) {
- return YES;
- } else {
- ((struct obj_layout *)anObject)[-1].retained--;
- return NO;
- }
- }
复制代码 当retained变量大于0时减1,等于0时调用dealloc实例方法,废弃对象。以下是废弃对象时所调用的dealloc实例方法的实现。
- - (void)dealloc {
- NSDeallocateObject(self);
- }
-
- inline void
- NSDeallocateObject(id anObject) {
- struct obj_layout *o = &((struct obj_layout *)anObject)[-1];
- free(o);
- }
复制代码 上述代码仅废弃由alloc分配的内存块。
以上是alloc/retain/release/dealloc在GNUstep中的实现。总结如下:
- OC对象中存有引用计数这一整数值
- 调用alloc或是retain方法,引用计数加一
- 调用release后,引用计数减一
- 引用计数值为零时,调用dealloc方法废弃对象
苹果的实现
接下来看看苹果是怎么实现内存管理和引用计数的。由于NSObject类的源代码没有公开,我们使用Xcode的调试器和iOS大概追溯出其实现过程。
在NSObejct类的alloc类方法上设置断点,追踪程序的执行,可以得到执行所调用的方法和函数。
可以看到alloc类方法是先调用allocWithZone:类方法和GNUstep的实现相同,然后调用class_createInstance函数,末了调用calloc来分配内存块。和前面GNUstep的实现差别不大。
那retainCount/retain/release实例方法又是怎么实现的呢?同刚才一样,先列出调用的方法和函数
可以看到每个方法都通过调用同一个__CFDoExternRefOperation函数,调用了一系列名称相似的函数。这些函数名前缀为"CF",它们包罗于Core Foundation框架源代码中。下面给出简化了CFDoExternRefOperation函数后的源代码(包罗在CFRuntime.c中)
- int __CFDoExternRefOperation(uintptr_t op, id obj) {
- CFBasicHashRef table = 取得对象对应的散列表(obj);
- int count;
-
- switch(op) {
- case OPERATION_retainCount:
- count = CFBasicHashGetCountOfKey(table, obj);
- return count;
- case OPERATION_retain:
- CFBasicHashAddValue(table, obj);
- return obj;
- case OPERATION_release:
- count = CFBasicHashRemoveValue(table, obj);
- return 0 == count;
- }
- }
复制代码 可以看到__CFDoExternRefOperation函数按retainCount/retain/release操纵进行分发,调用不同的函数,NSObejct类的retainCount/retain/release实例方法也许如下面代码所示:
- - (NSUInteger)retainCount {
- return (NSUInteger)__CFDoExternRefOperation(OPERATION_retain, self);
- }
-
- - (id)retain {
- return (id)__CFDoExternRefOperation(OPERATION_retain, self);
- }
-
- - (void)release {
- return __CFDoExternRefOperation(OPERATION_release, self);
- }
复制代码 可见,苹果对内存管理的实现大概就是接纳散列表(引用计数表)来管理引用计数。
GNUstep将引用计数保存在对象占用内存块头部的变量中,而苹果的实现,则是保存在引用计数表的记载中,两者各有好处:
通过内存块头部管理引用计数有以下好处:
- 少量代码即可完成
- 能够统一管理引用计数用内存块与对象用内存块
通过引用计数表管理引用计数有以下好处:
- 对象用内存块的分配无需考虑内存块头部
- 引用计数表各记载中存有内存块地址,可以从各个记载追溯到各对象的内存块
这里第二条特性在调试时有很重要的作用,纵然出现故障导致对象占用的内存块损坏,但只要引用计数表没有被破坏,就能确认各内存块的位置
autorelease
autorelease就是自动释放,类似于C语言中局部变量的特性。autorelease会像C语言的局部变量那样来对待对象实例,当超出其作用域时,对象实例的release实例方法被调用。但是与C语言的局部变量不同的是,编程人员可以设定变量的作用域
autorelease的详细使用方法如下:
- 天生并持有NSAutoreleasePool对象
- 调用已分配对象的autorelease实例方法
- 废弃NSAutoreleasePool对象
NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于全部调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。用源代码表现如下:
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- id obj = [[NSObject alloc] init];
- [obj autorelease];
- [pool drain];
复制代码 在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行天生、持有和废弃处理。因此,开辟者不肯定非得使用NSAutoreleasePool对象来进行开辟工作
只管如此,在大量产生autorelease的对象时,只要不废弃NSAutoreleasePool对象,那么天生的对象就不能被释放,因此偶然会产生内存不敷的现象。
Cocoa框架中有许多类方法用于返回autorelease的对象,比如NSMutableArray类的arrayWithCapacity类方法。
- id array = [NSMutableArray arrayWithCapacity:1];
- id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
复制代码 autorelease实现
首先看GNUstep的源代码:
- - (id)autorelease{
- [NSAutoreleasePool addObject:self];
- }
复制代码 autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。
下面是简化之后的NSAutoreleasePool类的源代码:
- + (void)addObject:(id)anObj {
- NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;
- if (pool != nil) {
- [pool addObject:anObj];
- } else {
- NSLog(@"NSAutoreleasePool对象非存在状态下调用autorelease");
- }
- }
复制代码 假如嵌套天生或持有的NSAutoreleasePool对象,理所当然会使用最内侧的对象。比如下例中pool2为正在使用的NSAutoreleasePool对象
- NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];
- NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
- NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
- id obj = [[NSObject alloc] init];
- [obj autorelease];
- [pool2 drain];
- [pool1 drain];
- [pool0 drain];
复制代码 这里addObject实例方法是这样实现的:
- - (void)addObject:(id)anObj {
- [array addObject:anObj];
- }
复制代码 实际的GNUstep实现使用的是连接列表,这同在NSMutableArray对象中追加对象参数是一样的。假如调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组里。既然NSAutoreleasePool实例底层是用数组实现的,那么在使用drain实例方法时同样有处理数组的过程
- - (void)drain {
- [self dealloc];
- }
-
- - (void)dealloc {
- [self emptyPool];
- [array release];
- }
-
- - (void)emptyPool {
- for (id obj in array) {
- [obj release];
- }
- }
复制代码 这样可以确保对数组中的全部对象都调用了release实例方法
苹果的实现
可通过objc4库的runtime/obj-arr.mm来确认苹果中autorelease的实现。
- class AutoreleasePoolPage {
- static inline void *push() {
- 相当于生成或持有NSAutoreleasePool类对象;
- }
- static inline void *pop(void *token) {
- 相当于废弃NSAutoreleasePool类对象
- releaseAll();
- }
- static inline id autorelease(id obj) {
- 相当于NSAutoreleasePool类的addObject类方法
- AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例;
- autoreleasePoolPage->add(obj);
- }
- id *add (id obj) {
- 将对象追加到内部数组中;
- }
- void releaseAll() {
- 调用内部数组中对象的release实例方法;
- }
- };
- void *objc_autoreleasePoolPush(void) {
- return AutoreleasePoolPage::push();
- }
- void objc_autoreleasePoolPop(void *ctxt) {
- AutoreleasePoolPage::pop(ctxt);
- }
- id *objc_autorelease(id obj) {
- return AutoreleasePoolPage::autorelease(obj);
- }
复制代码 C++类中虽然有动态数组的实现,但其行为和GNUstep的实现完全相同。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |