目录:
- 内存管理概述
- 内存管理的实现相关的几个C函数
- ARC下的一些规则
- @property中关键字与所有权修饰符的对应关系
- 静态数组与动态数组在内存管理上的差异
内存管理概述
# 原则
- 自己生成的对象,自己所持有
- 非自己所生成的对象,自己也能持有
- 自己持有的对象自己释放
- 非自己持有的对象无法释放
# 核心
内存管理的核心即是引用计数,散列表管理
实现的管理手段可以分为:手动管理、自动释放池
## MRC下的实现(不做赘述了)
手动release、retain、autorelease
## ARC下的实现(ARC式的内存管理是编译器的工作
)
本质上是相同的,只是在源代码的书写方法上稍有不同,引入了所有权修饰符,来协助完成内存管理工作
__strong
__weak
__unsafe_unretained
__autoreleasing
内存管理的实现相关的几个C函数
内存区域可以分为栈,堆,可读写区(全部变量与静态变量)和只读区(常量与代码段)。局部变量,函数形参,临时变量都是在栈上获得内存的,它们获取的方式都是由编译器自动执行的。
C 标准函数库提供了许多函数来实现对堆上内存管理,其中包括:malloc
函数,free
函数,calloc
函数和realloc
函数。使用这些函数需要包含头文件stdlib.h
- malloc函数
malloc函数可以从堆上获得指定字节的内存空间,其函数声明如下:
/*
* @param n 要求分配的字节数
* @return 如果函数执行成功,malloc返回获得内存空间的首地址;如果函数执行失败,那么返回值为NULL
*/
void * malloc(int n);
由于malloc函数值的类型为void型指针,因此,可以将其值类型转换后赋给任意类型指针,这样就可以通过操作该类型指针来操作从堆上获得的内存空间。
需要注意的是:malloc函数分配得到的内存空间是未初始化的。因此,一般在使用该内存空间时,必须要调用另一个函数memset来将其初始化为全0。
- 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));
- free函数
从堆上获得的内存空间在程序结束以后,系统不会将其自动释放,需要程序员来自己管理。一个程序结束时,必须保证所有从堆上获得的内存空间已被安全释放,否则,会导致内存泄露。
free函数可以实现释放内存的功能。
/*
* @param p 要释放的void类型指针
* @return 如果函数执行成功,mall
*/
void free (void * p);
free函数只是释放指针指向的内容,而该指针仍然指向原来指向的地方,此时,指针为野指针,如果此时操作该指针会导致不可预期的错误。安全做法是:在使用free函数释放指针指向的空间之后,将指针的值置为NULL。
free(p);
p=NULL;
注意:使用malloc函数分配的堆空间在程序结束之前必须释放。
- 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函数的分配的内存也需要自行释放。
- 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,那么就会被释放。
# 动态数组
动态数组即长度不固定的数组
- 动态数组,需要手动释放所有的元素。因为编译器不能确定动态数组的生存周期,所以不能自动插入释放赋值对象的代码。