基本原理
简介
内存管理是程序设计中常见的资源管理的一部分。每个计算机系统可供程序使用的资源都是有限的,这些资源包括内存、打开文件、数量及网络连接等等。如果你使用了某种资源,例如因为打开文件而占用了资源,那么你需要随后对其进行清理。如果我们只分配而不释放内存,则将发生内存泄露:程序的内存占用不断增加,最终会耗尽并导致程序奔溃。
管理范围:任何继承NSObject的对象,对其他的基本数据类型无效。
什么是内存?
答:程序运行中临时分配的存储空间,在程序结束后释放;本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。
什么是内存管理?
答:内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。
我们为什么要进行内存管理?
程序运行时,内存资源是有限的,Objective-C编写程序过程中创建实例化对象会占用内存,程序使用的内存会随着程序中对象的增加而不断增加,久而久之,最终系统资源被消耗殆尽。Mac OS 有垃圾回收机制,但iOS没有,iOS内存远不足于Mac OS系统,iOS将内存管理的任务交给了开发者。
程序内存分配
堆区:有程序员分配释放,若程序员不释放,可能会造成程序泄露。注意,它与数据结构中的堆是两回事,分配方式倒是类似于链表;
栈区:由编译器自动分配释放,存放函数的参数值和局部变量的值;
全局区(静态区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。
程序代码区:存放函数体的二进制代码。
对象的基本结构
每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。
在每个OC对象内部,都专门有4个字节的存储空间来存储引用计数器。
引用计数概念
- 判断对象要不要回收的唯一依据就是计数器是否为0,若不为0则存在。
引用计数操作
给对象发送消息,进行相应的计数器操作。下图为对象操作与Objective-c方法的对应:
对象操作 | Objective-c方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy等方法 |
释放对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
相关概念和使用注意
野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)。
僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用。(打开僵尸对象检测)
空指针:没有指向任何东西的指针(存储的东西是:0、NULL、nil),给空指针发送消息不会报错。
Tips
1、不能使用[p retaion]让僵尸对象起死复生。
内存管理原则
原则
1、 只要还有人在使用某个对象,那么这个对象就不会被回收;
2、只要你想使用这个对象,那么就应该让这个对象的引用计数器+1;
3、当你不想使用这个对象时,应该让对象的引用计数器-1;
谁创建,谁release
1、如果你通过alloc、new、copy以及mutableCopy来创建了一个对象,那么你就必须调用release或者autorelease方法;
2、不是你创建的就不用你去负责;
谁retain,谁release
1、只要你调用了retain,无论这个对象时如何生成的,你都要调用release
总结
- 有始有终,有加就应该有减。曾经让某个对象计数器+1,就应该让其在最后-1;
手动管理内存设置方法
使用Xcode4.2或以上版本,系统默认采用ARC管理内存,即:自动引用技术(ARC,Automatic Reference Counting),ARC是指内存管理中对引用采取自动计数的技术。ARC机制下,引用计数操作如retain、release等将无法使用,需切换到手动管理内存。
要从自动引用计数回归到手动引用技术,实现步骤如下:
选择工程目录,进入TARGETS >点击Build Settings > 在搜索框输入 automatic 搜索 >选择Objective-C Automatic Reference Counting 将其列表值置为NO即可。如下图
内存管理代码规范
set方法的代码规范
- 基本数据类型:直接赋值
- (void)setAge:(NSInteger)age {
_age = age;
}
- OC对象类型
- (void)setName:(NSString *)name {
// 判断是否为新传入的对象
if (name != _name) {
// 对旧对象做一次release释放
[_name release];
// 对新对象做一次retain持有
_name = [name retain];
}
}
dealloc方法的代码规范
- 对self(当前)所拥有的的其他对象做一次release操作
- (void)dealloc {
[_name release];
[super dealloc];
}
遍历构造方法的代码规范
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age {
Person *person = [[Person alloc] initWithName:name age:age];
return [person autorelease];
}
Tips
1、一定要
[super dealloc];
,而且要放到最后
@property属性参数
内存管理相关参数
1、Retain:对对象release旧值,retain新值(适用于OC对象类型)
2、Assign:直接赋值(默认,适用于非oc对象类型)
3、Copy:release旧值,copy新值
是否要生成set方法(若为只读属性,则不生成)
1、Readonly:只读,只会生成getter的声明和实现
2、Readwrite:默认的,同时生成setter和getter的声明和实现
多线程管理
1、Nonatomic:高性能,一般使用这个
2、Atomic:低性能
Set和Get方法的名称
修改set和get方法的名称,主要用于布尔类型。因为返回布尔类型的方法名一般以is开头,修改名称一般用在布尔类型中的getter。
@propery(setter = setLogin, getter = isLogin) BOOL login;
BOOL b = person.isLogin; // 调用
内存管理中的循环引用问题以及解决
案例:每个老师都有学生,每个学生都有老师,不能使用#import的方式相互包含,这就形成了循环引用。
解决办法:使用@class 类名
,解决循环引用问题,提高性能。
@class仅仅告诉编译器,在进行编译的时候把后面的名字作为一个类来处理。
@class仅仅告诉编译器,在进行编译的时候把后面的名字作为一个类来处理。
开发中引用一个类的规范
在.h文件中使用@class来声明类
在.m文件中真正要使用到的时候,使用#import来包含类中的所有东西
两端循环引用的解决方法:一端使用retain,一端使用assign(使用assign的在dealloc中也不用再release)
Autorelease
基本用法
会将对象放到一个自动释放池中
当自动释放池被销毁时,会对池子里的所有对象做一次release
会返回对象本身
调用完autorelease方法后,对象的计数器不受影响(销毁时影响)
好处
不需要再关心对象释放的时间
不需要再关心什么时候调用release
使用注意
占用内存较大的对象,不要随便使用autorelease,应该使用release来精确控制
占用内存较小的对象使用autorelease,没有太大的影响
错误写法
连续调用多次autorelease,释放池销毁时执行两次release。
Alloc之后调用了autorelease,之后又调用了release。
自动释放池
在ios程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。
当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
自动释放池的创建方式
- iOS 5.0 以前的创建方式
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
// .....
[pool release]; // 用于 OX S环境
- iOS 5.0 以后
@autoreleasepool
{//开始代表创建自动释放池
·······
}//结束代表销毁自动释放池
Autorelease注意
系统自带的方法中,如果不包含alloc new copy等,则这些方法返回的对象都是autorelease的,如[NSDate date];
开发中经常会写一些类方法来快速创建一个autorelease对象,创建对象时不要直接使用类名,而是使用self。
ARC内存管理机制
ARC判断标准
- 只要没有强指针指向对象,对象就会被释放。
指针分类:
强指针:默认的情况下,所有的指针都是强指针,关键字strong
弱指针:__weak关键字修饰的指针
1、声明一个弱指针如下:
__weak Person *p;
2、ARC中,只要弱指针指向的对象不在了,就直接把弱指针做清空操作。
__weak Person *p = [[Person alloc] init]; // 不合理,对象一创建出来就被释放掉,对象释放掉后,ARC把指针自动清零。
3、ARC中在property处不再使用retain,而是使用strong,在dealloc中不需要再[super dealloc]。
@property(nonatomic,strong)Person *person; // 意味着生成的成员变量_person是一个强指针,相当于以前的retain。
4、如果换成是弱指针,则换成weak,不需要加__
。
ARC的特点总结
不允许调用release,retain,retainCount
不允许重写dealloc,但是不允许调用[super dealloc]
@property的参数:
1、Strong:相当于原来的retain(适用于OC对象类型),成员 变量是强指针
2、Weak:相当于原来的assign(适用于oc对象类型),成员变量是弱指针
3、Assign:适用于非OC对象类型(基础类型)
补充
尽管目前系统提倡使用ARC技术,但是其自身也有一定的缺陷,大部分开发者还是使用非ARC管理内存,为了让程序兼容ARC和非ARC部分,需进行如下操作:
转变为非ARC: -fno-objc-arc
转变为ARC: -f-objc-arc
Tips
1、ARC也需要考虑循环引用问题:一端使用retain,另一端使用assign。
2、提示:字符串是特殊的对象,但不需要使用release手动释放,这种字符串对象默认就是autorelease的,不用额外的去管内存。