Objective-C内存篇(一) - 内存管理的思考方式、ARC下的规则

目录:

  • 内存管理概述
  • 内存管理的实现相关的几个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声明属性时,一些关键字与所有权修饰符的对象关系


2181780-01bf24d61c7c27c2.jpeg

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

# 静态数组

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

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

# 动态数组

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

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

没有更多推荐了,返回首页