美文网首页
自动引用技术

自动引用技术

作者: helinyu | 来源:发表于2021-08-30 10:50 被阅读0次

    ARC(automatic reference counting)【 自动引用技术】:是指内存管理中对引用采取自动计数的技术。

    类似的生活例子:办公司灯光的开启与关闭。


    >1.2.2内存管理的思考方式

    1)自己生成的对象,自己所持有
    2)非自己生成的对象,自己也能持有
    3)不再需要自己持有的对象时释放
    4)非自己持有的对象无法释放

    内存管理方法不包括在语言上,是包括在cocoa 框架中
    1、自己生成的对象,自己持有

    id obj =[[NSObject alloc] init]
    id obj = [NSObject new]
    自己生成并持有对象, 自己持有对象。
    copy/mutableCopy 方法生成并持有对象的副本。 和alloc/new 方法在“自己生成并持有对象” 这点上没有改变。
    根据上述“使用以下名称开头的方法名”,下列名称具备同样的功能意义:
    *) allocMyObject
    *) newThatObject
    *) copyThis
    *)mutableCopyYourObject
    但是以下开头的就不是同样的意义了
    *) allocate
    *) newer
    *) copying
    *)mutableCopyed

    1、 非自己生成的对象,自己也能持有

    取得非自己生成并持有的对象
    id obj = [NSMutableArray array];
    取得的对象存在,但自己不持有对象[里面包含了autorelease]
    [obj retain]
    自己持有对象, 通过retain方法
    现在在ARC上面是否也已经实现了里面的功能。

    为什么推荐使用alloc而不是new?
    new = [alloc] init] ; 采用new的方式只能采用默认的init方法完成初始化,采用alloc的方式可以用其他定制的初始化方法。

    array
    数据中的new和array的区别

    3、不再需要自己持有的对象时释放 (release)

    id obj = [[NSObject alloc] init] 自己生成并持有对象
    [obj release] 释放对象

    可以通过retain方法持有对象,一旦不再需要,务必用release方法进行释放。

    alloc 生成并持有对象, 但是array中是如何处理呢?
    array 方法是取得的对象存在,但自己不持有对象,又是如何实现的呢?

    • (id)object {
      id obj = [[NSObject alloc] init];
      [obj autorelease];
      return obj;
      }
      autorelease 方法可以使取得的对象存在,但自己不持有对象。


      autorelease 提供功能:使对象在超出指定的生存范围时能够自动并正确地释放(调用release方法)

      NOTE: 这些取得谁都不持有的对象的方法名不能够以alloc/new/copy/mutableCopy 开头

    4、无法释放非自己持有的对象

    alloc/new/copy/mutableCopy 生成并持有的对象、或者使用了retain持有的对象, 不需要的时候需要释放。
    此外得到的对象,所得的对象不需要释放。
    eg: 释放了再次释放

    1.2.3 alloc/retain/release/dealloc 实现 - gnustep

    NSDefaultMallocZone , NSZoneMalloc 等名称中包含的NSZone是什么呢? 它是为了防止内存碎片化而引入的结构。 对内存分配的区域本身进行多重化管理, 根据使用对象的目的、对象的大小分配内存,从而提高了内存管理的效率。

    struct obj_layout {
      char  padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__)
        ? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)];
      gsrefcount_t  retained; // 保存的引用技术,并将其写入对象内存头部,该对象内存块全部置0后返回。
    };
    typedef struct obj_layout *obj;
    

    PS: GNUstep上实现的OC内容,是将引用技术存放在对象的头部进行管理的。

    1.2.4 苹果的实现

    1、使用断点追踪程序的执行函数调用。

    方法调用栈

    • alloc
      +allocWithZone
      class_createInstance 【objc-runtime-new.mm】
      calloc
    有关引用技术的方法调用, 有关源码在runloop里面找到

    ??? 这里面有一个疑惑了,为什么runtime里面有retaincount 调用的难道不是这样?

    内存块头部管理引用计数的好处:
    1)少量代码即可完成
    2)能够统一管理引用计数用内存块与对象用内存块

    通过引用基数表管理引用计数的好处:
    1)对象用内存块的分配无需考虑内存块头部
    2)引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块。

    3)利用工具检测内存泄漏时,引用计数表的各记录也有助于检测各对象的持有者是否存在。

    // 这里就涉及到引用计数的实现内容, 这个地方要深入研究一下, runloop 之间和runtime之间的调用

    在 retain 方法加符号断点会发现 alloc, new, copy, mutableCopy 这四个方法都会通过 Core Foundation 的 CFBasicHashAddValue() 函数来调用 retain 方法。其实 CF 有个修改和查看引用计数的入口函数 __CFDoExternRefOperation,在 CFRuntime.c 文件中实现。 这个地方, 需要更加进一步去查看这个内容。

    1.2.5 autorelease (自动释放)

    (1) 看上去很想ARC, 但实际上它更类似于C语言中的自动变量(局部变量)的特性。
    (2) 局部变量,定义的变量,超出了它的作用于,就会废弃。

    autorelease 会像C语言的自动变量那样,来对待对象实例。 当超出其作用域时,对象实例的release实例方法被调用。 另外,同C语言的自动变量不同的是, 变成人员可以设置变量的作用域 。
    1)生成并持有NSAutoreleasePool对象
    2)调用已分配对象的autorelease实例方法
    3)废弃NSAutoreleasePool对象


    对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时, 都会调用release实例方法。

    有关autoreleasepool这个功能的实现 objc-internal.h 这个文件里面可以看到头文件。 在NSObject里面已经实现了这个功能。 @autoreleasepool {} 这个里面,应该就是我们调用实现的过程了。
    NSArray 中的array方法有关的内容

    在Cocoa框架中, 相当于程序主循环的NSRunloop或在其他程序可运行的地方, 对NSAutoreleasepool对象进行生成、持有和废弃处理。 因此,应用程序开发者不一定非得使用NSAutoreleasePool 对象来进行开发工作。


    但是大量产生autorelease的对象时, 只要不废弃NSAutoreleasePool对象, 那么生成的对象就不能够释放,因此有时候生成内存不足的对象。典型的例子是读入大量图像的同事改变其尺寸。 对象文件读入到NSData对象,并从生成UIImage对象,改变该对象尺寸后生成新的UIImage对象。 这种情况下,就会大量产生autorelease的对象。




    这种情况下,有必要在适当的地方生成、持有、或废弃NSAutoreleasePool对象。



    添加了释放的autoreleasepool 数组中经常会和这个有关系

    1.2.6 autorelease 实现 —— GNUstep

    也就是在GNUstep里面实现的autoreleasepool是使用链表的方式实现了autoreleasepool里面的嵌套关系。

    @interface NSAutoreleasePool : NSObject
    {
    #if GS_EXPOSE(NSAutoreleasePool) && !__has_feature(objc_arc)
      /* For re-setting the current pool when we are dealloc'ed. */
      NSAutoreleasePool *_parent;
      /* This pointer to our child pool is  necessary for co-existing
         with exceptions. */
      NSAutoreleasePool *_child; // 这里管理的是有关的嵌套,这里看出来,嵌套是没有分支的
        //     这里管理的是对象
      /* A collection of the objects to be released. */
      struct autorelease_array_list *_released;
      struct autorelease_array_list *_released_head;
      /* The total number of objects autoreleased in this pool. */
      unsigned _released_count;
      /* The method to add an object to this pool */
      void  (*_addImp)(id, SEL, id);
    #endif
    #if     GS_NONFRAGILE
    #else
      /* Pointer to private additional data used to avoid breaking ABI
       * when we don't have the non-fragile ABI available.
       * Use this mechanism rather than changing the instance variable
       * layout (see Source/GSInternal.h for details).
       */
      @private id _internal GS_UNUSED_IVAR;
    #endif
    }
    

    1.2.7 autoreleasepool实现 —— 苹果实现

    // 基本的数据结构, pool里面就是使用了page对象,page是集成了pageData对象
    class AutoreleasePoolPage;
    struct AutoreleasePoolPageData
    {
    #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
        struct AutoreleasePoolEntry {
            uintptr_t ptr: 48;
            uintptr_t count: 16;
    
            static const uintptr_t maxCount = 65535; // 2^16 - 1
        };
        static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
    #endif
    
        magic_t const magic;
        __unsafe_unretained id *next; // 这个应该也是指向的是当前的autoreleasepoolpage对象
        pthread_t const thread;
        
        AutoreleasePoolPage * const parent; // 父page
        AutoreleasePoolPage *child; // 子page
    //     可以看到这个也是一个量表
        uint32_t const depth; // 表示的是什么深度
        uint32_t hiwat; //?
    
    //     初始化这个内容
        AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
            : magic(), next(_next), thread(_thread),
              parent(_parent), child(nil),
              depth(_depth), hiwat(_hiwat)
        {
        }
    };
    
    
    PS : 从这个结构可以看出来,
    1》 pool里面的对象是使用链表实现
    2》pool和pool之间的关系也是使用了链表的关系。
    3》这里没有看出来,可以实现连个pool并列 ? 这个是为什么呢? 使用中,我们是可以并列的,为什么这个不行呢?难道这里使用了两个指针,里面的next和head
    
    数据的继承类
    //主要的两个方法
    OBJC_EXPORT
    void * _Nonnull
    objc_autoreleasePoolPush(void)
        OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
    
    OBJC_EXPORT
    void
    objc_autoreleasePoolPop(void * _Nonnull context)
        OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
    
    // 下面两个方法是调用前面的两个方法
    OBJC_EXPORT void * _Nonnull
    _objc_autoreleasePoolPush(void)
        OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
    
    OBJC_EXPORT void
    _objc_autoreleasePoolPop(void * _Nonnull context)
        OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
    OBJC_EXPORT void
    _objc_autoreleasePoolPrint(void)
        OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); 
    // 打印方法, 这个是调用了printAll 方法
    
    
      __attribute__((noinline, cold))
        static void printAll()
        {
            _objc_inform("##############");
            _objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());
    
            AutoreleasePoolPage *page;
            ptrdiff_t objects = 0; // 获取有关的对象
            for (page = coldPage(); page; page = page->child) {
                objects += page->next - page->begin();
            }
            _objc_inform("%llu releases pending.", (unsigned long long)objects); // 打印出来这个内容
    
            if (haveEmptyPoolPlaceholder()) { // 没有了
                _objc_inform("[%p]  ................  PAGE (placeholder)", 
                             EMPTY_POOL_PLACEHOLDER);
                _objc_inform("[%p]  ################  POOL (placeholder)", 
                             EMPTY_POOL_PLACEHOLDER);
            }
            else {
                for (page = coldPage(); page; page = page->child) { 
    // 变量里面嵌套的pool
                    page->print();
                }
            }
    
            _objc_inform("##############");
        }
    

    image.png
    image.png

    autorelease 这个地方需要深入研究一下。


    1.3 ARC 规则

    编译器进行了ARC的内容处理,里面还是使用了“引用计式内存管理”

    1.3.3 所有权修饰符

    __strong
    __weak
    __unsafe_unretained
    __autoreleasing

    《1》__strong 修饰符

    __strong 修饰符是id类型和对象类型默认的所有权修饰符。 "强引用"。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
    id obj = [NSObject new];
    _>
    id __strong obj = [NSObject new];

    __strong 修饰的, 超出作用域范围之后,就会释放掉。

    还有特殊的:array] 方法


    __strong 修饰符通后面要讲的__weak 修饰符 和__autoreleasing 修饰符一起,可以保证将附有的自动变量初始化为nil。

    通过__strong 修饰符, 不必要再键入retain或者release ,完美地满足了“引用计数式内存管理的思考方式”。
    因为id烈性和对象类型的所有权修饰符默认为__strong 修饰符, 所以不要写上__strong 。

    《2》__weak 修饰符

    (1) strong修饰符无法解决“循环引用”的问题。
    (2) 循环引用容易发生内存泄漏。 所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。
    (2)eg: 两个对象之间的相互应用, 自身引用自身。

    ————> weak 若引用,避免了循环引用。

    没有强引用的弱引用,将会被释放掉
    修改

    使用weak修饰符可以避免循环应用,可以检查附有__weak 修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

    《3》__unsafe_unretained 修饰符 【不安全的所有权修饰符】

    尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unreatined 修饰符的变量不属于编译器的内存管理对象。

            id __unsafe_unretained obj = [NSObject new];
    //生产出有关的警告:
    Assigning retained object to unsafe_unretained variable; object will be released after assignment
    

    虽然使用__unsafe_unretained 修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能够继续为自己所有,所以生成的对象会被立即释放。 到这里,__unsafe_unretained 修饰符和weak是一样的。


    image.png
    image.png
    造成了野指针

    unsafe_retained 这个修饰符确保其变量的值是存在的。 否在造成程序奔溃。 而weak可以自动置为nil。

    《4》__autorelease 修饰符


    ARC 环境下使用: 
      @autoreleasepool { 。。。。 }
    



    因为没有显示指定所有权修饰符, 所以id obj同附有strong修饰符的id strong obj 是完全一样的。 由于return使得对象变量超出其作用域,所以,该强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。




    NSError **





    这个内容,需要进一步去整理整理。
    ——————

    1.3.4 规则
    image.png
    1.3.5 属性
    ARC 环境下
    1.3.6 数组 【暂略】

    有关数组的内容, 以后再细看一下, 【暂略】

    相关文章

      网友评论

          本文标题:自动引用技术

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