内存管理/引用计数 - alloc/retain/release

2018-09-02  本文已影响23人  关灯侠

所有内容引用自《Objective-C 高级编程 iOS与OS X多线程和内存管理》,加入了自己的部分理解。

本节小结,点小1跳到底部[1]
第一节、思考方式
第三节、autorelease实现


这里分两种实现,GNUstep实现,苹果的实现。
为啥要研究GNUstep实现?Cocoa不开源,没办法知道苹果实现。GNUstep开源,并且是Cocoa的互换框架。


GNUstep实现

1、alloc实现

// GNUstep/modules/core/base/Source/NSObject.m 
+ (id) alloc{
    return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(NSZone *)z{
    return NSAllocateObject (self, 0 , z);
}

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];
}

NSZone是防止内存碎片化引入的结构。对内存分配的区域进行多重化管理,根据使用对象的目的、对象大小分配内存,提高内存管理的效率。(就是小内存是一段连续空间,大内存是一段连续空间。)

因为iOS运行时系统的内存管理已经极具效率,所以目前已经忽略了区域的概念。

alloc简化区域后的实现:

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);
}

其中retained保存引用计数,写入到对象头部。

alloc返回的指针.png

对象引用计数,可以使用retainCount获取到。实现如下:

- (NSUInteger)retainCount{
    return NSExtraRefCount(self) + 1;
}

inline NSUInteger
NSExtraRefCount(id anObject){
    // 由对象寻址找到对象头部,取出retained值返回
    return ((struct obj_layout *) anObject) [-1].retained;
}

2、retain实现

- (id)retain{
    NSIncrementExtraRefCount(self);
    return self;
}

inline void
NSIncrementExtraRefCount(id anObject){
    // 如果超过最大值,给出提示
    if(((struct obj_layout *) anObject) [-1].retained == UNNT_MAX - 1){
      [NSException raise:NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];
  }else{ // +1
    ((struct obj_layout *) anObject) [-1].retained++;
  }
}

3、release实现

- (void)release{
    if (NSIncrementExtraRefCountWasZero(self)){
      [self dealloc];
  }
}

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

4、dealloc实现

- (void)dealloc{
    NSDeallocateObject(self);
}

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

小结:


苹果实现

NSObject源码没有公开,只能通过断点来追踪程序的执行。
1、alloc的实现

+alloc
+allocWithZone:
// 创建对象
class_createInstance
// 分配内存
calloc

2、retainCount/retain/release的实现

// retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
// retain
__CFDoExternRefOperation
CFBasicHashAddValue
// release
__CFDoExternRefOperation
CFBasicHashRemoveValue
// CFBasicHashRemoveValue返回0时,会调用dealloc

CF前缀,是使用了Core Foundation框架。
__CFDoExternRefOperationCFRuntime.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);

        case  OPERATION_release:
              count = CFBasicHashRemoveValue(table,obj);
              return 0 == count;
    }
}

大致可以得出,采用的是哈希表来管理引用计数的,表健值是内存块地址。
前面GNUstep实现,是把引用计数放在对象内存块头部。和苹果相比各自的好处:

✨✨✨
放在头部的好处:

✨✨✨✨✨
使用哈希表作为引用计数表的好处:

第二点特别重要,即使内存块损坏了,只要引用计数表没有破坏,就可以确认内存块的位置。特别是检测内存泄露时,可以帮助定位各对象是否持有。


小结

1、可以通过GNUstep源码来推测苹果实现。
2、alloc\retain 引用计数+1。
3、release引用计数-1,为0时,调用dealloc废弃对象。
4、引用计数由哈希表管理,方便追溯内存块,定位内存泄露。


  1. 😊假装是锚点的脚注

上一篇下一篇

猜你喜欢

热点阅读