【IOS学习】之五:引用计数

arc  automatic reference counting   内存管理中对引用采取自动计数。

apple官方文档:    在oc中采用arc机制,让编译器来进行内存管理,在新一代apple llvm编译器中设置arc为有效状态,就无需再次键入retain或release代码,降低程序崩溃,内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此一来,应用程序将具有可预测性,并且能流程运行,运行速度也将大幅提升。

来说一下引用计数:  比如上班,  最早进入办公室的人需要开灯,之后进入办公室的人需要照明, 下班离开办公室的人不需要照明,最后离开办公室的人需要关灯。
这样对应的引用计数就是:第一个人进入办公室开灯,引用计数是1.  之后进入办公室需要照明  引用计数是2 。  下班一个人离开办公室 引用计数变成了1  最后一个离开了办公室,引用计数变成了0 。

在内存管理中:  自己生成的对象,自己持有。  
                          不是自己生成的对象,自己也能持有。 
                          不再需要自己持有的对象就是放。 
                          不是自己持有的对象无法释放。
生成并持有对象:alloc/new/copy/mutableCopy     
          持有对象:retain  
          释放对象: release  
          废弃对象: dealloc
这些内存管理是在 cocoa框架 中的foundation框架类库的NSObject类担负的。

autorelease 是使对象在超出指定的生存范围时能够自动并正确地释放。


我们在释放对象的时候,不能释放不是自己持有的对象。
ex:   
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
[obj release];
//对象已释放
[obj release];
//释放之后再次释放已非自己持有的对象,应用程序崩溃。     
//崩溃情况: 再度废弃已经废弃了的对象时崩溃,    访问已经废弃的对象时崩溃

我们取得对象,但是自己不持有对象:
//取得对象,但是自己并不持有对象
id obj1 = [obj0 object];
//释放不是自己持有的对象,应用程序崩溃
[obj1 release];


来说一个框架:GNUstep  他是和 cocoa框架的互换框架。他们的行为和实现方式是一样的,相似的。
在gnusetp中,alloc的实现如下:
/**
* Allocates a new instance of the receiver from the default
* zone, by invoking +allocWithZone: with
* <code>NSDefaultMallocZone()</code> as the zone argument.<br />
* Returns the created instance.
*/
+ (id) alloc
{
  return [self allocWithZone: NSDefaultMallocZone()];
}

 * <p>
*   If you have turned on debugging of object allocation (by
*   calling the <code>GSDebugAllocationActive</code>
*   function), this method will also update the various
*   debugging counts and monitors of allocated objects, which
*   you can access using the <code>GSDebugAllocation...</code>
*   functions.
* </p>
*/
+ (id) allocWithZone: (NSZone*)z
{
  return NSAllocateObject (self, 0, z);
}


通过allocWithZone: 类方法调用NSAllocateObject函数分配对象。
/*
*     Now do the REAL version - using the other version to determine
*     what padding (if any) is required to get the alignment of the
*     structure correct.
*/
struct obj_layout {
    char     padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__)
      ? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)];
    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];
}


这里是通过NSZoneMalloc来分配存放对象所需的内存空间,之后将该内存空间置0   最后返回作为对象而是用的指针。



这里的NSZone解释一下:  是为了防止内存碎片化而引入的结构,对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高了内存管理的效率。



去掉NSZone的源代码:
struct obj_layout {
     NSUInteger retained;
};
+ (id) alloc {
     int size = sizeof (struct obj_layout) + 对象大小;
     struct obj_layout *p = (struct obj_alyout*)calloc(1, size);
     return (id)(p + 1);
}


这里是用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部。  对象内存块 全部置0后返回。

通过retainCount来返回:
/**
* Returns the reference count for the receiver.  Each instance has an
* implicit reference count of 1, and has an ‘extra reference count‘
* returned by the NSExtraRefCount() function, so the value returned by
* this method is always greater than zero.<br />
* By convention, objects which should (or can) never be deallocated
* return the maximum unsigned integer value.
*/
- (NSUInteger) retainCount
{
#if     GS_WITH_GC
  return UINT_MAX;
#else
  return NSExtraRefCount(self) + 1;
#endif
}
/**
* Return the extra reference count of anObject (a value in the range
* from 0 to the maximum unsigned integer value minus one).<br />
* The retain count for an object is this value plus one.
*/
inline NSUInteger
NSExtraRefCount(id anObject)
{
#ifdef __OBJC_GC__
  if (objc_collecting_enabled())
    {
      return UINT_MAX-1;
    }
#endif
#if     GS_WITH_GC
  return UINT_MAX - 1;
#else     /* GS_WITH_GC */
  return ((obj)anObject)[-1].retained;
#endif /* GS_WITH_GC */
}


由对象寻址找到对象内存头部,从而访问其中的retained变量。


retain方法:
/**
* Increments the reference count and returns the receiver.<br />
* The default implementation does this by calling NSIncrementExtraRefCount()
*/
- (id) retain
{
#if     (GS_WITH_GC == 0)
  NSIncrementExtraRefCount(self);
#endif
  return self;
}

/**
* Increments the extra reference count for anObject.<br />
* The GNUstep version raises an exception if the reference count
* would be incremented to too large a value.<br />
* This is used by the [NSObject-retain] method.
*/
inline void
NSIncrementExtraRefCount(id anObject)
#endif
{
#if     GS_WITH_GC || __OBJC_GC__
  return;
#else     /* GS_WITH_GC */
  if (allocationLock != 0)
    {
#if     defined(GSATOMICREAD)
      /* I‘ve seen comments saying that some platforms only support up to
       * 24 bits in atomic locking, so raise an exception if we try to
       * go beyond 0xfffffe.
       */
      if (GSAtomicIncrement((gsatomic_t)&(((obj)anObject)[-1].retained))
        > 0xfffffe)
     {
       [NSException raise: NSInternalInconsistencyException
         format: @"NSIncrementExtraRefCount() asked to increment too far"];
     }
#else     /* GSATOMICREAD */
      NSLock *theLock = GSAllocationLockForObject(anObject);

      [theLock lock];
      if (((obj)anObject)[-1].retained == UINT_MAX - 1)
     {
       [theLock unlock];
       [NSException raise: NSInternalInconsistencyException
         format: @"NSIncrementExtraRefCount() asked to increment too far"];
     }
      ((obj)anObject)[-1].retained++;
      [theLock unlock];
#endif     /* GSATOMICREAD */
    }
  else
    {
      if (((obj)anObject)[-1].retained == UINT_MAX - 1)
     {
       [NSException raise: NSInternalInconsistencyException
         format: @"NSIncrementExtraRefCount() asked to increment too far"];
     }
      ((obj)anObject)[-1].retained++;
    }
#endif     /* GS_WITH_GC */
}




release的实现:
/**
* Decrements the retain count for the receiver if greater than zero,
* otherwise calls the dealloc method instead.<br />
* The default implementation calls the NSDecrementExtraRefCountWasZero()
* function to test the extra reference count for the receiver (and
* decrement it if non-zero) - if the extra reference count is zero then
* the retain count is one, and the dealloc method is called.<br />
* In GNUstep, the [NSObject+enableDoubleReleaseCheck:] method may be used
* to turn on checking for ratain/release errors in this method.
*/
- (oneway void) release
{
#if     (GS_WITH_GC == 0)
  if (NSDecrementExtraRefCountWasZero(self))
    {
#  ifdef OBJC_CAP_ARC
      objc_delete_weak_refs(self);
#  endif
      [self dealloc];
    }
#endif
}

BOOL NSDecrementExtraRefCountWasZero(id anObject) {
     if (((struct obj_layout *)anObject)[-1].retained == 0) {
          return YES;
     } else {
          ((struct obj_layout *)anObject)[-1].retained--;
          return NO;
     }

}


dealloc实现:
 
* <p>
*   If you have allocated the memory using a non-standard mechanism, you
*   will not call the superclass (NSObject) implementation of the method
*   as you will need to handle the deallocation specially.<br />
*   In some circumstances, an object may wish to prevent itself from
*   being deallocated, it can do this simply be refraining from calling
*   the superclass implementation.
* </p>
*/
- (void) dealloc
{
  NSDeallocateObject (self);
}

inline void NSDeallocateObject(id anObject) {
     struct obj_layout *o = &((struct obj_layout*)anObject)[-1];
     free(o);
}


在oc的对象中存有引用计数这一整数值。
调用alloc或者retain方法后,引用计数+1;
调用release后,引用计数-1;
引用计数值为0时,调用dealloc方法废弃对象。


分析其cocoa的实现:
在NSObject的alloc上下断点,可以看到调用函数:
+alloc
+allocWithZone:
class_createInstance
calloc
这里alloc类方法首先调用allocWithZone:类方法, 跟GNUstep相同。然后调用class_createInstance函数。最后用calloc来分配内存。
retainCount:
-retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey

retain:
-retain
__CFDoExternRefOperation
CFBasicHashAddValue

release:
-release
__CFDoExternRefOperation
CFBasicHashRemoveValue      (CFBasicHashRemoveValue返回0时, -release调用dealloc)

int __CFDoExternRefOperation(uintptr_t op, id obj) {
     CFBasicHashRef table = init;
     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;
     }
}

- (NSUInteger)retainCount {
     return (NSUInteger) __CFDoExternRefOperation(OPERATION_retainCount, self);
}

- (id)retain {
     return (id)__CFDoExternRefOperation(OPERATION_retain, self);
}

- (void)release {
     return __CFDoExternRefOperation(OPERATION_release, self);
}


从函数中看出,apple用的是hash来管理引用计数。



来说一下两种内存管理:
1、通过内存块头部管理引用计数好处:
     少量代码就能完成。
     能够统一管理引用计数用内存块与对象用内存块。
2、通过引用计数表管理引用计数好处:
     对象用内存块的分配无需考虑内存块头部。
     引用计数表各项记录中存有内存块地址,可从各个记录追溯到各个对象的内存块。

在第二条中,追溯到内存块 在调试中是很重要的。只要引用计数表没有被破坏就能找到内存块的位置。


autorelease的实现: 类比c的作用域概念。

使用方法:1、生成并持有NSAutoreleasePool对象。
               2、调用已分配对象的autorelease实例方法。
               3、废弃NSAutoreleasePool对象。


在cocoa框架中,程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。
当我们大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放。有时候会产生内存不足的情况。
我们可以在必要的地方持有,废弃:
for (int i = 0; i < count; ++i) {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     ***
     [pool drain];
}


在NSObject中,aurelease是这样运作的:
- (id) autorelease {
     [NSAutoreleasePool addObject:self];
}


addObject 是将对象连接上去,即GNUstep使用的是连接列表。  如NSMutableArray也是一样的。


apple对autorelea的实现:
class AutoreleasePoolPage {
     static inline void *push() {
          生成或持有NSAutoreleasePool类对象。
     }
     static inline void *pop(void *token) {
          废弃NSAutoreleasePool类对象;
          releaseAll();
     }
     static inline id autorelease(id obj) {
          相当于NSAutoreleasePool类的addObject类方法。
     }
     id *add(id obj) {
          追加; 
     }
     void releaseAll() {
          调用内部数组中的对象的release实例方法。
     }
}
void *objc_autoreleasePoolPush(void) {
     return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
     AutoreleasePoolPage:pop(ctxt);
}
id *obj_autorelease( id obj) {
     return AutoreleasePoolPage::autorelease(obj);
}



要注意的是当我们 pool autorelease会怎样?
这样做会发生异常,  在ob中,也就是foundation框架,无论调用那个对象的autorelease实例方法,实现上是调用的都是NSObject类的autorelease实例方法。
但是对于NSAutoreleasePool类,autorelease实例方法已经被该类重载了,所以出现了错误。


最后:  如何提高调用oc方法的速度
     gnustep中,autorelease是用IMP(函数指针) Caching来实现的,他能高效地运行os x,ios应用程序频繁调用autorelease方法。
在方法调用时,为了解决类名、方法名以及取得方法运行时的函数指针,要在框架初始化时对其结果值进行缓存。
id autorelease_class [NSAutoreleasePool class];
SEL autorelease_sel = @selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
实际:
- (id)autorelease {
     (*autorelease_imp)(autorelease_class, autorelease_sel, self);
}
与
- (id)autorelease {
     [NSAutoreleasePool addObject:self];
}
作用相同,但是第一种方法运行效率会快2倍。 但是他依赖于运行环境。

-----2014、3、14  beijing

【IOS学习】之五:引用计数,,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。