美文网首页
内存管理/引用计数 - alloc/retain/release

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

作者: 关灯侠 | 来源:发表于2018-09-02 13:19 被阅读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);
    }
    

    小结:

    • Objective-C对象中存有引用计数整数值
    • allocretain,引用计数+1
    • release引用计数-1
    • 引用计数为0时,调用dealloc废弃对象

    苹果实现

    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. 😊假装是锚点的脚注

    相关文章

      网友评论

          本文标题:内存管理/引用计数 - alloc/retain/release

          本文链接:https://www.haomeiwen.com/subject/kpuuwftx.html