美文网首页
Objective-C内存篇(一) - 内存管理的思考方式、AR

Objective-C内存篇(一) - 内存管理的思考方式、AR

作者: Tenloy | 来源:发表于2018-08-06 17:55 被阅读160次

    目录:

    • 内存管理概述
    • 内存管理的实现相关的几个C函数
    • ARC下的一些规则
    • @property中关键字与所有权修饰符的对应关系
    • 静态数组与动态数组在内存管理上的差异

    内存管理概述

    # 原则

    • 自己生成的对象,自己所持有
    • 非自己所生成的对象,自己也能持有
    • 自己持有的对象自己释放
    • 非自己持有的对象无法释放

    # 核心

    内存管理的核心即是引用计数,散列表管理
    实现的管理手段可以分为:手动管理、自动释放池

    ## MRC下的实现(不做赘述了)

    手动release、retain、autorelease

    ## ARC下的实现(ARC式的内存管理是编译器的工作

    本质上是相同的,只是在源代码的书写方法上稍有不同,引入了所有权修饰符,来协助完成内存管理工作
    __strong
    __weak
    __unsafe_unretained
    __autoreleasing

    内存管理的实现相关的几个C函数

    内存区域可以分为栈,堆,可读写区(全部变量与静态变量)和只读区(常量与代码段)。局部变量,函数形参,临时变量都是在栈上获得内存的,它们获取的方式都是由编译器自动执行的。

    C 标准函数库提供了许多函数来实现对堆上内存管理,其中包括:malloc函数,free函数,calloc函数和realloc函数。使用这些函数需要包含头文件stdlib.h

    1. malloc函数
    malloc函数可以从堆上获得指定字节的内存空间,其函数声明如下:
    /*
     * @param n 要求分配的字节数
     * @return  如果函数执行成功,malloc返回获得内存空间的首地址;如果函数执行失败,那么返回值为NULL
     */
    void * malloc(int n);
    
    由于malloc函数值的类型为void型指针,因此,可以将其值类型转换后赋给任意类型指针,这样就可以通过操作该类型指针来操作从堆上获得的内存空间。
    

    需要注意的是:malloc函数分配得到的内存空间是未初始化的。因此,一般在使用该内存空间时,必须要调用另一个函数memset来将其初始化为全0。

    1. memeset函数
    memset函数可以将指定的内存空间按字节单位置为指定的字符.
    
    memset函数的声明如下:
    /*
     * @param p 要清零的内存空间的首地址
     * @param c 要设定的值
     * @param n 被操作的内存空间的字节长度
     * @return  如果函数执行成功,malloc返回获得内存空间的首地址;如果函数执行失败,那么返回值为NULL
     */
      void * memset (void * p,int c,int n) ;
    
    如果要用memset清0,变量c实参要为0。malloc函数和memset函数的操作语句一般如下:
    
      int * p=NULL;
      p=(int *)malloc(sizeof(int));
      if(p==NULL) printf(“Can’t get memory!\n”);
      memset(p,0,siezeof(int));
    
    1. free函数

    从堆上获得的内存空间在程序结束以后,系统不会将其自动释放,需要程序员来自己管理。一个程序结束时,必须保证所有从堆上获得的内存空间已被安全释放,否则,会导致内存泄露。

    free函数可以实现释放内存的功能。
    /*
     * @param p 要释放的void类型指针
     * @return  如果函数执行成功,mall
     */
    void free (void * p);
    
    free函数只是释放指针指向的内容,而该指针仍然指向原来指向的地方,此时,指针为野指针,如果此时操作该指针会导致不可预期的错误。安全做法是:在使用free函数释放指针指向的空间之后,将指针的值置为NULL。
    
    free(p);
    p=NULL;
    
    注意:使用malloc函数分配的堆空间在程序结束之前必须释放。
    
    1. calloc函数
    calloc函数的功能与malloc函数的功能相似,都是从堆分配内存。
    /*
     * @param n  分配多少个
     * @param size 要求分配的单位字节数
     * @return  函数返回值为void型指针。如果执行成功,函数从堆上获得size X n的字节空间,并返回该空间的首地址。如果执行失败,函数返回NULL。
     */
    void *calloc(int n,int size);
    
    该函数与malloc函数的一个显著不同是:
        calloc函数得到的内存空间是经过初始化的,其内容全为0。
        calloc函数适合为数组申请空间,可以将size设置为数组元素的空间长度,将n设置为数组的容量。
    
    提示:calloc函数的分配的内存也需要自行释放。
    
    1. realloc函数
    realloc函数的功能比malloc函数和calloc函数的功能更为丰富,可以实现内存分配和内存释放的功能。
    /*
     * @param p 必须为指向堆内存空间的指针,即由malloc函数、calloc函数或realloc函数分配空间的指针
     * @param n  内存块大小
     * @return  首地址
     */
    void * realloc(void * p,int n);
    
    realloc函数将指针p指向的内存块的大小改变为n字节。
    如果n小于或等于p之前指向的空间大小,那么。保持原有状态不变。
    如果n大于原来p之前指向的空间大小,那么,系统将重新为p从堆上分配一块大小为n的内存空间,同时,将原来指向空间的内容依次复制到新的内存空间上。
    p之前指向的空间被释放。
    
    
    注意:
    1.relloc函数分配的空间也是未初始化的, 如果要使用realloc函数分配的内存,也是必须使用memset函数对其内存初始化。
    2. 如realloc函数重新分配的内存地址,有时候会改变,有时候不会改变
    

    注意:使用malloc函数,calloc函数和realloc函数分配的内存空间都要使用free函数或指针参数为NULL的realloc函数来释放。

    ARC下,内存管理需要遵循的一些规则

    # 须遵守内存管理的构造方法命名规则(MRC下最好也遵循)

    在MRC下:用于对象生成/持有的方法必须遵守以下的命名规则:方法名以alloc/new/copy/mutableCopy开头
    在ARC下:增加一条:init,且更为严格

    • 必须是实例方法,并且必须要返回对象
    • 返回的对象应为id类型或该方法声明类的对象类型,抑或是超类或子类
    • 返回的对象不注册autoreleasepool中

    # 不能使用retain/release/retainCount/autorelease

    ARC下,内存管理是编译器的工作,没有必要再使用内存管理的方法(retain/release/retainCount/autorelease)

    # 不能使用NSAllocateObject/NSDeallocateObject

    # 不能使用区域(NSZone)

    无论是否是ARC,NSZone在iOS 5 之后,就已经被忽略到了,即使使用,也不会生效。

    # 不能显式调用dealloc

    无论是ARC/MRC,只要对象被废弃,都会自动调用这个函数,进而调用free函数释放对象

    # 使用@autoreleasepool块替代NSAutoreleasePool

    # 对象型变量不能作为C语言结构体(struct/union)的成员

    原因:

    • ARC下的内存管理其实是编译器的工作,所以编译器必须能够知道并管理对象的生存周期。
    • 对于C语言来说,自动变量(局部变量)可以使用该变量的作用域来管理对象,但是C语言的规约上,并没有方法来管理结构体成员的生存周期!

    解决方案:

    • 将对象型变量强制转换为void *
    • 附加__unsafe__unretained修饰符(__unsafe_unretained修饰符的变量是不属于编译器的内存管理对象范围),但是需要注意内存泄漏或野指针的问题。

    # 显式转换id 和 void *

    可以认为id = void *,都是用于隐藏对象类型的类名部分
    接下来的转换,与其说是id 和 void * 转换,不如说是Foundation与Core Foundation对象转换

    ## __bridge转换
    void *p = (__bridge void *)obj;  
    但是其安全性与__unsafe_unretained来修饰对象类变量差不多,甚至比后者更低,极有可能造成野指针
    
    id obj = (__bridge id)p;
    
    ## __bridge_transfer__bridge_retained
    Objective-C变量 = (__bridge_transfer <#Objective-C type#>)CF变量
    理解:
      1. 被转换的CF变量在该变量被赋值给 转换目标变量 后随之被释放。
      2. 然后目标变量即OC对象就接着由Foundation框架的方法来进行管理:MRC、ARC
    
    CF变量  = (__bridge_retained <#CF type#>)Objective-C变量
    理解:
      1. 使CF变量持有被赋值的OC变量
      2. 既然持有了,那也就需要释放,可以使用__bridge_transfer来释放:(void)(__bridge_transfer id)p; 
    
    也可以使用另外两个封装的函数来实现
    CFTypeRef CFBridgingRetain(id X) {
      return (__bridge_retained CFTypeRef)X;
    }
    
    id CFBridgingRelease(CFTypeRef X) {
        return (__bridge_transfer id)X; 
    }
    

    CoreFoundation与Foundation对象没有区别,所以简单的转换即可实现,另外,这种转换不需要使用额外的CPU资源,因此也被称为免费桥

    @property声明属性的关键字与所有权修饰符的对应关系

    ARC下,用@property声明属性时,一些关键字与所有权修饰符的对象关系


    静态数组与动态数组在内存管理上的差异

    # 静态数组

    静态数组即长度固定的数组

    • __strong/__weak/__autoreleasing修饰符修饰的静态数组,能保证其初始化为nil
    • 静态数组在超出其变量作用域时,随着数组变量的强引用消失,数组中的各个变量也会失去一个强引用,如果引用计数此时为0,那么就会被释放。

    # 动态数组

    动态数组即长度不固定的数组

    • 动态数组,需要手动释放所有的元素。因为编译器不能确定动态数组的生存周期,所以不能自动插入释放赋值对象的代码。

    相关文章

      网友评论

          本文标题:Objective-C内存篇(一) - 内存管理的思考方式、AR

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